{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":15444,"sourceType":"datasetVersion","datasetId":11102},{"sourceId":12667361,"sourceType":"datasetVersion","datasetId":8004945}],"dockerImageVersionId":31090,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-08-13T14:46:01.118184Z","iopub.execute_input":"2025-08-13T14:46:01.118859Z","iopub.status.idle":"2025-08-13T14:46:01.140479Z","shell.execute_reply.started":"2025-08-13T14:46:01.118836Z","shell.execute_reply":"2025-08-13T14:46:01.139873Z"}},"outputs":[{"name":"stdout","text":"/kaggle/input/cifar10-python/cifar-10-python.tar.gz\n/kaggle/input/cifar10-python/cifar-10-batches-py/data_batch_1\n/kaggle/input/cifar10-python/cifar-10-batches-py/data_batch_2\n/kaggle/input/cifar10-python/cifar-10-batches-py/batches.meta\n/kaggle/input/cifar10-python/cifar-10-batches-py/test_batch\n/kaggle/input/cifar10-python/cifar-10-batches-py/data_batch_3\n/kaggle/input/cifar10-python/cifar-10-batches-py/data_batch_5\n/kaggle/input/cifar10-python/cifar-10-batches-py/data_batch_4\n/kaggle/input/cifar10-python/cifar-10-batches-py/readme.html\n/kaggle/input/tensor/tensorcore.py\n","output_type":"stream"}],"execution_count":10},{"cell_type":"markdown","source":"安装库","metadata":{}},{"cell_type":"code","source":"!pip uninstall torch torchvision torchaudio -y\n!pip cache purge\n# 安装兼容组合（torch 2.6.0 + torchvision 0.21.0 + tensorrt 2.6.0）\n!pip install torch==2.6.0 torchvision==0.21.0 torchaudio==2.6.0\n\n# 安装对应的 torch-tensorrt（2.6.0）\n!pip install https://github.com/pytorch/TensorRT/releases/download/v2.6.0/torch_tensorrt-2.6.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_34_x86_64.whl","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-13T14:02:34.307624Z","iopub.execute_input":"2025-08-13T14:02:34.307997Z","iopub.status.idle":"2025-08-13T14:07:34.792432Z","shell.execute_reply.started":"2025-08-13T14:02:34.307975Z","shell.execute_reply":"2025-08-13T14:07:34.791382Z"}},"outputs":[{"name":"stdout","text":"Found existing installation: torch 2.6.0+cu124\nUninstalling torch-2.6.0+cu124:\n  Successfully uninstalled torch-2.6.0+cu124\nFound existing installation: torchvision 0.21.0+cu124\nUninstalling torchvision-0.21.0+cu124:\n  Successfully uninstalled torchvision-0.21.0+cu124\nFound existing installation: torchaudio 2.6.0+cu124\nUninstalling torchaudio-2.6.0+cu124:\n  Successfully uninstalled torchaudio-2.6.0+cu124\n\u001b[33mWARNING: No matching packages\u001b[0m\u001b[33m\n\u001b[0mFiles removed: 0\nCollecting torch==2.6.0\n  Downloading torch-2.6.0-cp311-cp311-manylinux1_x86_64.whl.metadata (28 kB)\nCollecting torchvision==0.21.0\n  Downloading torchvision-0.21.0-cp311-cp311-manylinux1_x86_64.whl.metadata (6.1 kB)\nCollecting torchaudio==2.6.0\n  Downloading torchaudio-2.6.0-cp311-cp311-manylinux1_x86_64.whl.metadata (6.6 kB)\nRequirement already satisfied: filelock in /usr/local/lib/python3.11/dist-packages (from torch==2.6.0) (3.18.0)\nRequirement already satisfied: typing-extensions>=4.10.0 in /usr/local/lib/python3.11/dist-packages (from torch==2.6.0) (4.14.0)\nRequirement already satisfied: networkx in /usr/local/lib/python3.11/dist-packages (from torch==2.6.0) (3.5)\nRequirement already satisfied: jinja2 in /usr/local/lib/python3.11/dist-packages (from torch==2.6.0) (3.1.6)\nRequirement already satisfied: fsspec in /usr/local/lib/python3.11/dist-packages (from torch==2.6.0) (2025.5.1)\nCollecting nvidia-cuda-nvrtc-cu12==12.4.127 (from torch==2.6.0)\n  Downloading nvidia_cuda_nvrtc_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl.metadata (1.5 kB)\nCollecting nvidia-cuda-runtime-cu12==12.4.127 (from torch==2.6.0)\n  Downloading nvidia_cuda_runtime_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl.metadata (1.5 kB)\nCollecting nvidia-cuda-cupti-cu12==12.4.127 (from torch==2.6.0)\n  Downloading nvidia_cuda_cupti_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl.metadata (1.6 kB)\nCollecting nvidia-cudnn-cu12==9.1.0.70 (from torch==2.6.0)\n  Downloading nvidia_cudnn_cu12-9.1.0.70-py3-none-manylinux2014_x86_64.whl.metadata (1.6 kB)\nCollecting nvidia-cublas-cu12==12.4.5.8 (from torch==2.6.0)\n  Downloading nvidia_cublas_cu12-12.4.5.8-py3-none-manylinux2014_x86_64.whl.metadata (1.5 kB)\nCollecting nvidia-cufft-cu12==11.2.1.3 (from torch==2.6.0)\n  Downloading nvidia_cufft_cu12-11.2.1.3-py3-none-manylinux2014_x86_64.whl.metadata (1.5 kB)\nCollecting nvidia-curand-cu12==10.3.5.147 (from torch==2.6.0)\n  Downloading nvidia_curand_cu12-10.3.5.147-py3-none-manylinux2014_x86_64.whl.metadata (1.5 kB)\nCollecting nvidia-cusolver-cu12==11.6.1.9 (from torch==2.6.0)\n  Downloading nvidia_cusolver_cu12-11.6.1.9-py3-none-manylinux2014_x86_64.whl.metadata (1.6 kB)\nCollecting nvidia-cusparse-cu12==12.3.1.170 (from torch==2.6.0)\n  Downloading nvidia_cusparse_cu12-12.3.1.170-py3-none-manylinux2014_x86_64.whl.metadata (1.6 kB)\nRequirement already satisfied: nvidia-cusparselt-cu12==0.6.2 in /usr/local/lib/python3.11/dist-packages (from torch==2.6.0) (0.6.2)\nRequirement already satisfied: nvidia-nccl-cu12==2.21.5 in /usr/local/lib/python3.11/dist-packages (from torch==2.6.0) (2.21.5)\nRequirement already satisfied: nvidia-nvtx-cu12==12.4.127 in /usr/local/lib/python3.11/dist-packages (from torch==2.6.0) (12.4.127)\nCollecting nvidia-nvjitlink-cu12==12.4.127 (from torch==2.6.0)\n  Downloading nvidia_nvjitlink_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl.metadata (1.5 kB)\nRequirement already satisfied: triton==3.2.0 in /usr/local/lib/python3.11/dist-packages (from torch==2.6.0) (3.2.0)\nRequirement already satisfied: sympy==1.13.1 in /usr/local/lib/python3.11/dist-packages (from torch==2.6.0) (1.13.1)\nRequirement already satisfied: numpy in /usr/local/lib/python3.11/dist-packages (from torchvision==0.21.0) (1.26.4)\nRequirement already satisfied: pillow!=8.3.*,>=5.3.0 in /usr/local/lib/python3.11/dist-packages (from torchvision==0.21.0) (11.2.1)\nRequirement already satisfied: mpmath<1.4,>=1.1.0 in /usr/local/lib/python3.11/dist-packages (from sympy==1.13.1->torch==2.6.0) (1.3.0)\nRequirement already satisfied: MarkupSafe>=2.0 in /usr/local/lib/python3.11/dist-packages (from jinja2->torch==2.6.0) (3.0.2)\nRequirement already satisfied: mkl_fft in /usr/local/lib/python3.11/dist-packages (from numpy->torchvision==0.21.0) (1.3.8)\nRequirement already satisfied: mkl_random in /usr/local/lib/python3.11/dist-packages (from numpy->torchvision==0.21.0) (1.2.4)\nRequirement already satisfied: mkl_umath in /usr/local/lib/python3.11/dist-packages (from numpy->torchvision==0.21.0) (0.1.1)\nRequirement already satisfied: mkl in /usr/local/lib/python3.11/dist-packages (from numpy->torchvision==0.21.0) (2025.2.0)\nRequirement already satisfied: tbb4py in /usr/local/lib/python3.11/dist-packages (from numpy->torchvision==0.21.0) (2022.2.0)\nRequirement already satisfied: mkl-service in /usr/local/lib/python3.11/dist-packages (from numpy->torchvision==0.21.0) (2.4.1)\nRequirement already satisfied: intel-openmp<2026,>=2024 in /usr/local/lib/python3.11/dist-packages (from mkl->numpy->torchvision==0.21.0) (2024.2.0)\nRequirement already satisfied: tbb==2022.* in /usr/local/lib/python3.11/dist-packages (from mkl->numpy->torchvision==0.21.0) (2022.2.0)\nRequirement already satisfied: tcmlib==1.* in /usr/local/lib/python3.11/dist-packages (from tbb==2022.*->mkl->numpy->torchvision==0.21.0) (1.4.0)\nRequirement already satisfied: intel-cmplr-lib-rt in /usr/local/lib/python3.11/dist-packages (from mkl_umath->numpy->torchvision==0.21.0) (2024.2.0)\nRequirement already satisfied: intel-cmplr-lib-ur==2024.2.0 in /usr/local/lib/python3.11/dist-packages (from intel-openmp<2026,>=2024->mkl->numpy->torchvision==0.21.0) (2024.2.0)\nDownloading torch-2.6.0-cp311-cp311-manylinux1_x86_64.whl (766.7 MB)\n\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m766.7/766.7 MB\u001b[0m \u001b[31m2.1 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m:00:01\u001b[0m00:01\u001b[0m\n\u001b[?25hDownloading torchvision-0.21.0-cp311-cp311-manylinux1_x86_64.whl (7.2 MB)\n\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m7.2/7.2 MB\u001b[0m \u001b[31m90.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m:00:01\u001b[0m00:01\u001b[0m\n\u001b[?25hDownloading torchaudio-2.6.0-cp311-cp311-manylinux1_x86_64.whl (3.4 MB)\n\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m3.4/3.4 MB\u001b[0m \u001b[31m82.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m:00:01\u001b[0m\n\u001b[?25hDownloading nvidia_cublas_cu12-12.4.5.8-py3-none-manylinux2014_x86_64.whl (363.4 MB)\n\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m363.4/363.4 MB\u001b[0m \u001b[31m2.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m:00:01\u001b[0m00:01\u001b[0m\n\u001b[?25hDownloading nvidia_cuda_cupti_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl (13.8 MB)\n\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m13.8/13.8 MB\u001b[0m \u001b[31m93.7 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m:00:01\u001b[0m0:01\u001b[0m\n\u001b[?25hDownloading nvidia_cuda_nvrtc_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl (24.6 MB)\n\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m24.6/24.6 MB\u001b[0m \u001b[31m73.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m:00:01\u001b[0m00:01\u001b[0m\n\u001b[?25hDownloading nvidia_cuda_runtime_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl (883 kB)\n\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m883.7/883.7 kB\u001b[0m \u001b[31m35.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n\u001b[?25hDownloading nvidia_cudnn_cu12-9.1.0.70-py3-none-manylinux2014_x86_64.whl (664.8 MB)\n\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m664.8/664.8 MB\u001b[0m \u001b[31m2.5 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m:00:01\u001b[0m00:01\u001b[0m\n\u001b[?25hDownloading nvidia_cufft_cu12-11.2.1.3-py3-none-manylinux2014_x86_64.whl (211.5 MB)\n\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m211.5/211.5 MB\u001b[0m \u001b[31m8.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m:00:01\u001b[0m00:01\u001b[0m\n\u001b[?25hDownloading nvidia_curand_cu12-10.3.5.147-py3-none-manylinux2014_x86_64.whl (56.3 MB)\n\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m56.3/56.3 MB\u001b[0m \u001b[31m30.5 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m:00:01\u001b[0m00:01\u001b[0m\n\u001b[?25hDownloading nvidia_cusolver_cu12-11.6.1.9-py3-none-manylinux2014_x86_64.whl (127.9 MB)\n\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m127.9/127.9 MB\u001b[0m \u001b[31m13.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m00:01\u001b[0m00:01\u001b[0m\n\u001b[?25hDownloading nvidia_cusparse_cu12-12.3.1.170-py3-none-manylinux2014_x86_64.whl (207.5 MB)\n\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m207.5/207.5 MB\u001b[0m \u001b[31m4.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m:00:01\u001b[0m00:01\u001b[0m\n\u001b[?25hDownloading nvidia_nvjitlink_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl (21.1 MB)\n\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m21.1/21.1 MB\u001b[0m \u001b[31m75.1 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m:00:01\u001b[0m00:01\u001b[0m\n\u001b[?25hInstalling collected packages: nvidia-nvjitlink-cu12, nvidia-curand-cu12, nvidia-cufft-cu12, nvidia-cuda-runtime-cu12, nvidia-cuda-nvrtc-cu12, nvidia-cuda-cupti-cu12, nvidia-cublas-cu12, nvidia-cusparse-cu12, nvidia-cudnn-cu12, nvidia-cusolver-cu12, torch, torchaudio, torchvision\n  Attempting uninstall: nvidia-nvjitlink-cu12\n    Found existing installation: nvidia-nvjitlink-cu12 12.5.82\n    Uninstalling nvidia-nvjitlink-cu12-12.5.82:\n      Successfully uninstalled nvidia-nvjitlink-cu12-12.5.82\n  Attempting uninstall: nvidia-curand-cu12\n    Found existing installation: nvidia-curand-cu12 10.3.6.82\n    Uninstalling nvidia-curand-cu12-10.3.6.82:\n      Successfully uninstalled nvidia-curand-cu12-10.3.6.82\n  Attempting uninstall: nvidia-cufft-cu12\n    Found existing installation: nvidia-cufft-cu12 11.2.3.61\n    Uninstalling nvidia-cufft-cu12-11.2.3.61:\n      Successfully uninstalled nvidia-cufft-cu12-11.2.3.61\n  Attempting uninstall: nvidia-cuda-runtime-cu12\n    Found existing installation: nvidia-cuda-runtime-cu12 12.5.82\n    Uninstalling nvidia-cuda-runtime-cu12-12.5.82:\n      Successfully uninstalled nvidia-cuda-runtime-cu12-12.5.82\n  Attempting uninstall: nvidia-cuda-nvrtc-cu12\n    Found existing installation: nvidia-cuda-nvrtc-cu12 12.5.82\n    Uninstalling nvidia-cuda-nvrtc-cu12-12.5.82:\n      Successfully uninstalled nvidia-cuda-nvrtc-cu12-12.5.82\n  Attempting uninstall: nvidia-cuda-cupti-cu12\n    Found existing installation: nvidia-cuda-cupti-cu12 12.5.82\n    Uninstalling nvidia-cuda-cupti-cu12-12.5.82:\n      Successfully uninstalled nvidia-cuda-cupti-cu12-12.5.82\n  Attempting uninstall: nvidia-cublas-cu12\n    Found existing installation: nvidia-cublas-cu12 12.5.3.2\n    Uninstalling nvidia-cublas-cu12-12.5.3.2:\n      Successfully uninstalled nvidia-cublas-cu12-12.5.3.2\n  Attempting uninstall: nvidia-cusparse-cu12\n    Found existing installation: nvidia-cusparse-cu12 12.5.1.3\n    Uninstalling nvidia-cusparse-cu12-12.5.1.3:\n      Successfully uninstalled nvidia-cusparse-cu12-12.5.1.3\n  Attempting uninstall: nvidia-cudnn-cu12\n    Found existing installation: nvidia-cudnn-cu12 9.3.0.75\n    Uninstalling nvidia-cudnn-cu12-9.3.0.75:\n      Successfully uninstalled nvidia-cudnn-cu12-9.3.0.75\n  Attempting uninstall: nvidia-cusolver-cu12\n    Found existing installation: nvidia-cusolver-cu12 11.6.3.83\n    Uninstalling nvidia-cusolver-cu12-11.6.3.83:\n      Successfully uninstalled nvidia-cusolver-cu12-11.6.3.83\nSuccessfully installed nvidia-cublas-cu12-12.4.5.8 nvidia-cuda-cupti-cu12-12.4.127 nvidia-cuda-nvrtc-cu12-12.4.127 nvidia-cuda-runtime-cu12-12.4.127 nvidia-cudnn-cu12-9.1.0.70 nvidia-cufft-cu12-11.2.1.3 nvidia-curand-cu12-10.3.5.147 nvidia-cusolver-cu12-11.6.1.9 nvidia-cusparse-cu12-12.3.1.170 nvidia-nvjitlink-cu12-12.4.127 torch-2.6.0 torchaudio-2.6.0 torchvision-0.21.0\nCollecting torch-tensorrt==2.6.0\n  Downloading https://github.com/pytorch/TensorRT/releases/download/v2.6.0/torch_tensorrt-2.6.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_34_x86_64.whl (3.8 MB)\n\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m3.8/3.8 MB\u001b[0m \u001b[31m5.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m:00:01\u001b[0m00:01\u001b[0m\n\u001b[?25hRequirement already satisfied: torch==2.6.0 in /usr/local/lib/python3.11/dist-packages (from torch-tensorrt==2.6.0) (2.6.0)\nCollecting tensorrt<10.8.0,>=10.7.0.post1 (from torch-tensorrt==2.6.0)\n  Downloading tensorrt-10.7.0.post1.tar.gz (35 kB)\n  Preparing metadata (setup.py) ... \u001b[?25l\u001b[?25hdone\nCollecting tensorrt-cu12<10.8.0,>=10.7.0.post1 (from torch-tensorrt==2.6.0)\n  Downloading tensorrt_cu12-10.7.0.post1.tar.gz (18 kB)\n  Preparing metadata (setup.py) ... \u001b[?25l\u001b[?25hdone\nCollecting tensorrt-cu12-bindings<10.8.0,>=10.7.0 (from torch-tensorrt==2.6.0)\n  Downloading tensorrt_cu12_bindings-10.7.0.post1-cp311-none-manylinux_2_17_x86_64.whl.metadata (628 bytes)\nCollecting tensorrt-cu12-libs<10.8.0,>=10.7.0 (from torch-tensorrt==2.6.0)\n  Downloading tensorrt_cu12_libs-10.7.0.post1.tar.gz (710 bytes)\n  Installing build dependencies ... \u001b[?25l\u001b[?25hdone\n  Getting requirements to build wheel ... \u001b[?25l\u001b[?25hdone\n  Preparing metadata (pyproject.toml) ... \u001b[?25l\u001b[?25hdone\nRequirement already satisfied: packaging>=23 in /usr/local/lib/python3.11/dist-packages (from torch-tensorrt==2.6.0) (25.0)\nRequirement already satisfied: numpy in /usr/local/lib/python3.11/dist-packages (from torch-tensorrt==2.6.0) (1.26.4)\nRequirement already satisfied: typing-extensions>=4.7.0 in /usr/local/lib/python3.11/dist-packages (from torch-tensorrt==2.6.0) (4.14.0)\nRequirement already satisfied: filelock in /usr/local/lib/python3.11/dist-packages (from torch==2.6.0->torch-tensorrt==2.6.0) (3.18.0)\nRequirement already satisfied: networkx in /usr/local/lib/python3.11/dist-packages (from torch==2.6.0->torch-tensorrt==2.6.0) (3.5)\nRequirement already satisfied: jinja2 in /usr/local/lib/python3.11/dist-packages (from torch==2.6.0->torch-tensorrt==2.6.0) (3.1.6)\nRequirement already satisfied: fsspec in /usr/local/lib/python3.11/dist-packages (from torch==2.6.0->torch-tensorrt==2.6.0) (2025.5.1)\nRequirement already satisfied: nvidia-cuda-nvrtc-cu12==12.4.127 in /usr/local/lib/python3.11/dist-packages (from torch==2.6.0->torch-tensorrt==2.6.0) (12.4.127)\nRequirement already satisfied: nvidia-cuda-runtime-cu12==12.4.127 in /usr/local/lib/python3.11/dist-packages (from torch==2.6.0->torch-tensorrt==2.6.0) (12.4.127)\nRequirement already satisfied: nvidia-cuda-cupti-cu12==12.4.127 in /usr/local/lib/python3.11/dist-packages (from torch==2.6.0->torch-tensorrt==2.6.0) (12.4.127)\nRequirement already satisfied: nvidia-cudnn-cu12==9.1.0.70 in /usr/local/lib/python3.11/dist-packages (from torch==2.6.0->torch-tensorrt==2.6.0) (9.1.0.70)\nRequirement already satisfied: nvidia-cublas-cu12==12.4.5.8 in /usr/local/lib/python3.11/dist-packages (from torch==2.6.0->torch-tensorrt==2.6.0) (12.4.5.8)\nRequirement already satisfied: nvidia-cufft-cu12==11.2.1.3 in /usr/local/lib/python3.11/dist-packages (from torch==2.6.0->torch-tensorrt==2.6.0) (11.2.1.3)\nRequirement already satisfied: nvidia-curand-cu12==10.3.5.147 in /usr/local/lib/python3.11/dist-packages (from torch==2.6.0->torch-tensorrt==2.6.0) (10.3.5.147)\nRequirement already satisfied: nvidia-cusolver-cu12==11.6.1.9 in /usr/local/lib/python3.11/dist-packages (from torch==2.6.0->torch-tensorrt==2.6.0) (11.6.1.9)\nRequirement already satisfied: nvidia-cusparse-cu12==12.3.1.170 in /usr/local/lib/python3.11/dist-packages (from torch==2.6.0->torch-tensorrt==2.6.0) (12.3.1.170)\nRequirement already satisfied: nvidia-cusparselt-cu12==0.6.2 in /usr/local/lib/python3.11/dist-packages (from torch==2.6.0->torch-tensorrt==2.6.0) (0.6.2)\nRequirement already satisfied: nvidia-nccl-cu12==2.21.5 in /usr/local/lib/python3.11/dist-packages (from torch==2.6.0->torch-tensorrt==2.6.0) (2.21.5)\nRequirement already satisfied: nvidia-nvtx-cu12==12.4.127 in /usr/local/lib/python3.11/dist-packages (from torch==2.6.0->torch-tensorrt==2.6.0) (12.4.127)\nRequirement already satisfied: nvidia-nvjitlink-cu12==12.4.127 in /usr/local/lib/python3.11/dist-packages (from torch==2.6.0->torch-tensorrt==2.6.0) (12.4.127)\nRequirement already satisfied: triton==3.2.0 in /usr/local/lib/python3.11/dist-packages (from torch==2.6.0->torch-tensorrt==2.6.0) (3.2.0)\nRequirement already satisfied: sympy==1.13.1 in /usr/local/lib/python3.11/dist-packages (from torch==2.6.0->torch-tensorrt==2.6.0) (1.13.1)\nRequirement already satisfied: mpmath<1.4,>=1.1.0 in /usr/local/lib/python3.11/dist-packages (from sympy==1.13.1->torch==2.6.0->torch-tensorrt==2.6.0) (1.3.0)\nRequirement already satisfied: mkl_fft in /usr/local/lib/python3.11/dist-packages (from numpy->torch-tensorrt==2.6.0) (1.3.8)\nRequirement already satisfied: mkl_random in /usr/local/lib/python3.11/dist-packages (from numpy->torch-tensorrt==2.6.0) (1.2.4)\nRequirement already satisfied: mkl_umath in /usr/local/lib/python3.11/dist-packages (from numpy->torch-tensorrt==2.6.0) (0.1.1)\nRequirement already satisfied: mkl in /usr/local/lib/python3.11/dist-packages (from numpy->torch-tensorrt==2.6.0) (2025.2.0)\nRequirement already satisfied: tbb4py in /usr/local/lib/python3.11/dist-packages (from numpy->torch-tensorrt==2.6.0) (2022.2.0)\nRequirement already satisfied: mkl-service in /usr/local/lib/python3.11/dist-packages (from numpy->torch-tensorrt==2.6.0) (2.4.1)\nRequirement already satisfied: MarkupSafe>=2.0 in /usr/local/lib/python3.11/dist-packages (from jinja2->torch==2.6.0->torch-tensorrt==2.6.0) (3.0.2)\nRequirement already satisfied: intel-openmp<2026,>=2024 in /usr/local/lib/python3.11/dist-packages (from mkl->numpy->torch-tensorrt==2.6.0) (2024.2.0)\nRequirement already satisfied: tbb==2022.* in /usr/local/lib/python3.11/dist-packages (from mkl->numpy->torch-tensorrt==2.6.0) (2022.2.0)\nRequirement already satisfied: tcmlib==1.* in /usr/local/lib/python3.11/dist-packages (from tbb==2022.*->mkl->numpy->torch-tensorrt==2.6.0) (1.4.0)\nRequirement already satisfied: intel-cmplr-lib-rt in /usr/local/lib/python3.11/dist-packages (from mkl_umath->numpy->torch-tensorrt==2.6.0) (2024.2.0)\nRequirement already satisfied: intel-cmplr-lib-ur==2024.2.0 in /usr/local/lib/python3.11/dist-packages (from intel-openmp<2026,>=2024->mkl->numpy->torch-tensorrt==2.6.0) (2024.2.0)\nDownloading tensorrt_cu12_bindings-10.7.0.post1-cp311-none-manylinux_2_17_x86_64.whl (1.2 MB)\n\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m1.2/1.2 MB\u001b[0m \u001b[31m340.5 kB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m0:01\u001b[0m00:01\u001b[0m\n\u001b[?25hBuilding wheels for collected packages: tensorrt, tensorrt-cu12, tensorrt-cu12-libs\n  Building wheel for tensorrt (setup.py) ... \u001b[?25l\u001b[?25hdone\n  Created wheel for tensorrt: filename=tensorrt-10.7.0.post1-py2.py3-none-any.whl size=42170 sha256=149f253a6e30ad49bd0c4e4f939cadcbb73add6d55a20ce253efcac7d3e5ed69\n  Stored in directory: /root/.cache/pip/wheels/56/f9/fa/d8dc822675c08f0b9f87bb4f080164bb4cf206692e2e62c0ae\n  Building wheel for tensorrt-cu12 (setup.py) ... \u001b[?25l\u001b[?25hdone\n  Created wheel for tensorrt-cu12: filename=tensorrt_cu12-10.7.0.post1-py2.py3-none-any.whl size=17632 sha256=dbdcfeb79e7d929bb22db90ca323c910b1f350d52f4fc050872429568b26809f\n  Stored in directory: /root/.cache/pip/wheels/31/95/0d/93bc3231ac7802cf65d5600a1848f0052c3b34bf77a8138c28\n  Building wheel for tensorrt-cu12-libs (pyproject.toml) ... \u001b[?25l\u001b[?25hdone\n  Created wheel for tensorrt-cu12-libs: filename=tensorrt_cu12_libs-10.7.0.post1-py2.py3-none-manylinux_2_17_x86_64.whl size=2069981220 sha256=ab4b8ef46a113f2e704b4c755c4db89789d70069c806d50683e17811060a09e2\n  Stored in directory: /root/.cache/pip/wheels/7f/37/a3/80b753dffe437ee9003d15cefa3dfb451ade6e484737f97379\nSuccessfully built tensorrt tensorrt-cu12 tensorrt-cu12-libs\nInstalling collected packages: tensorrt-cu12-bindings, tensorrt-cu12-libs, tensorrt-cu12, tensorrt, torch-tensorrt\nSuccessfully installed tensorrt-10.7.0.post1 tensorrt-cu12-10.7.0.post1 tensorrt-cu12-bindings-10.7.0.post1 tensorrt-cu12-libs-10.7.0.post1 torch-tensorrt-2.6.0\n","output_type":"stream"}],"execution_count":3},{"cell_type":"markdown","source":"导入库","metadata":{}},{"cell_type":"code","source":"# ✅ 导入库\nimport torch\nimport torchvision\nimport torch.nn as nn\nimport torchvision.transforms as transforms\nimport torch_tensorrt\nimport time\nimport numpy as np\nfrom tqdm import tqdm\nfrom torchvision.models import mobilenet_v2\nfrom torch.utils.data import DataLoader\nfrom torchvision.datasets import CIFAR10\nfrom torchvision import models\nfrom torch.utils.data import DataLoader, random_split\nfrom tqdm import tqdm\nimport matplotlib.pyplot as plt\nimport os","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-13T14:51:31.812308Z","iopub.execute_input":"2025-08-13T14:51:31.812587Z","iopub.status.idle":"2025-08-13T14:51:31.817356Z","shell.execute_reply.started":"2025-08-13T14:51:31.812566Z","shell.execute_reply":"2025-08-13T14:51:31.816493Z"}},"outputs":[],"execution_count":25},{"cell_type":"markdown","source":"加载模型及数据集","metadata":{}},{"cell_type":"markdown","source":"**以下代码为训练代码，再次测试时使用的是训练好的导入的模型，不用再训练**","metadata":{}},{"cell_type":"code","source":"# ========== [1] 配置 ==========\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(\"使用设备：\", device)\n\nBATCH_SIZE = 32\nEPOCHS = 5\nSAVE_MODEL_PATH = \"mobilenetv2_cifar10.pth\"\nONNX_EXPORT_PATH = \"mobilenetv2_cifar10.onnx\"\n\n# ========== [2] 数据预处理和划分 ==========\ntransform = transforms.Compose([\n    transforms.Resize(224),  # 适配 MobileNetV2\n    transforms.ToTensor(),\n   transforms.Normalize((0.4914, 0.4822, 0.4465),\n                         (0.2023, 0.1994, 0.2010)),\n])\n\ndataset_path = '/kaggle/input/cifar10-python'\nfull_train = torchvision.datasets.CIFAR10(root=dataset_path, train=True, download=False, transform=transform)\n\n# 划分 80% 训练 + 20% 验证\ntrain_set, val_set = random_split(full_train, [40000, 10000])\n\n# 测试集\ntest_set = torchvision.datasets.CIFAR10(root=dataset_path, train=False, download=False, transform=transform)\n\n# DataLoaders\ntrain_loader = torch.utils.data.DataLoader(train_set, batch_size=BATCH_SIZE, shuffle=True)\nval_loader = torch.utils.data.DataLoader(val_set, batch_size=BATCH_SIZE, shuffle=False)\ntest_loader = torch.utils.data.DataLoader(test_set, batch_size=BATCH_SIZE, shuffle=False)\nprint(f\"train_set包含 {len(train_set)} 张图片\")\nprint(f\"val_set包含 {len(val_set)} 张图片\")\nprint(f\"test_set包含 {len(test_set)} 张图片\")\n\n# ========== [3] 构建 MobileNetV2 模型 ==========\n# model = models.mobilenet_v2(weights=None)\nmodel = mobilenet_v2(pretrained=True)\n# model.load_state_dict(torch.load('/kaggle/input/pretrain/mobilenet_v2-b0353104.pth'))\nmodel.classifier[1] = nn.Linear(model.last_channel, 10)  # 10 类\nmodel = model.to(device)\n\n# ========== [4] 启用混合精度训练（自动使用 TensorCore） ==========\nscaler = torch.cuda.amp.GradScaler() if torch.cuda.is_available() else None\ncriterion = nn.CrossEntropyLoss()\noptimizer = torch.optim.Adam(model.parameters(), lr=1e-4)\n\n# ========== [5] 训练 + 验证 ==========\ntrain_losses, val_losses, val_accuracies = [], [], []\n\nfor epoch in range(EPOCHS):\n    model.train()\n    running_loss = 0.0\n    loop = tqdm(train_loader, desc=f\"Epoch {epoch+1}/{EPOCHS}\")\n\n    for inputs, labels in loop:\n        inputs, labels = inputs.to(device), labels.to(device)\n        optimizer.zero_grad()\n\n        with torch.amp.autocast(device_type=device.type):\n            outputs = model(inputs)\n            loss = criterion(outputs, labels)\n\n        scaler.scale(loss).backward()\n        scaler.step(optimizer)\n        scaler.update()\n\n        running_loss += loss.item()\n        loop.set_postfix(loss=loss.item())\n\n    epoch_train_loss = running_loss / len(train_loader)\n    train_losses.append(epoch_train_loss)\n\n    # === 验证阶段 ===\n    model.eval()\n    val_loss = 0.0\n    correct, total = 0, 0\n    with torch.no_grad():\n        for inputs, labels in val_loader:\n            inputs, labels = inputs.to(device), labels.to(device)\n            outputs = model(inputs)\n            loss = criterion(outputs, labels)\n            val_loss += loss.item()\n\n            preds = outputs.argmax(dim=1)\n            correct += (preds == labels).sum().item()\n            total += labels.size(0)\n\n    avg_val_loss = val_loss / len(val_loader)\n    val_acc = correct / total\n    val_losses.append(avg_val_loss)\n    val_accuracies.append(val_acc)\n\n    print(f\"[Epoch {epoch+1}] Train Loss: {epoch_train_loss:.4f} | Val Loss: {avg_val_loss:.4f} | Val Acc: {val_acc*100:.2f}%\")\n\n# ========== [6] 测试集评估 ==========\nmodel.eval()\ntest_loss = 0.0\ncorrect, total = 0, 0\n\nwith torch.no_grad():\n    for inputs, labels in test_loader:\n        inputs, labels = inputs.to(device), labels.to(device)\n        outputs = model(inputs)\n        loss = criterion(outputs, labels)\n        test_loss += loss.item()\n\n        preds = outputs.argmax(dim=1)\n        correct += (preds == labels).sum().item()\n        total += labels.size(0)\n\navg_test_loss = test_loss / len(test_loader)\ntest_acc = correct / total\nprint(f\"\\n✅ Final Test Loss: {avg_test_loss:.4f} | Test Accuracy: {test_acc*100:.2f}%\")\n\n# ========== [7] 可视化 ==========\nplt.figure(figsize=(12, 5))\n\nplt.subplot(1, 2, 1)\nplt.plot(train_losses, label='Train Loss')\nplt.plot(val_losses, label='Val Loss')\nplt.legend()\nplt.title(\"Loss over Epochs\")\n\nplt.subplot(1, 2, 2)\nplt.plot(val_accuracies, label='Val Accuracy')\nplt.legend()\nplt.title(\"Validation Accuracy over Epochs\")\n\nplt.tight_layout()\nplt.show()\n\n# ========== [8] 保存模型 ==========\ntorch.save(model.state_dict(), SAVE_MODEL_PATH)\nprint(f\"✅ 模型参数已保存：{SAVE_MODEL_PATH}\")\n\n# ========== [7] 导出为 ONNX ==========\nmodel.eval()\ndummy_input = torch.randn(1, 3, 224, 224).to(device)\n\ntorch.onnx.export(\n    model,\n    dummy_input,\n    ONNX_EXPORT_PATH,\n    input_names=[\"input\"],\n    output_names=[\"output\"],\n    dynamic_axes={\"input\": {0: \"batch_size\"}, \"output\": {0: \"batch_size\"}},\n    opset_version=11\n)\n\nprint(f\"✅ ONNX 模型已导出：{ONNX_EXPORT_PATH}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-13T14:27:49.081914Z","iopub.execute_input":"2025-08-13T14:27:49.082183Z","iopub.status.idle":"2025-08-13T14:40:38.710482Z","shell.execute_reply.started":"2025-08-13T14:27:49.082165Z","shell.execute_reply":"2025-08-13T14:40:38.709737Z"}},"outputs":[{"name":"stdout","text":"使用设备： cuda\n","output_type":"stream"},{"name":"stderr","text":"Downloading: \"https://download.pytorch.org/models/mobilenet_v2-b0353104.pth\" to /root/.cache/torch/hub/checkpoints/mobilenet_v2-b0353104.pth\n","output_type":"stream"},{"name":"stdout","text":"train_set包含 40000 张图片\nval_set包含 10000 张图片\ntest_set包含 10000 张图片\n","output_type":"stream"},{"name":"stderr","text":"100%|██████████| 13.6M/13.6M [00:00<00:00, 112MB/s] \nEpoch 1/5: 100%|██████████| 1250/1250 [02:07<00:00,  9.77it/s, loss=0.333] \n","output_type":"stream"},{"name":"stdout","text":"[Epoch 1] Train Loss: 0.4158 | Val Loss: 0.2271 | Val Acc: 92.05%\n","output_type":"stream"},{"name":"stderr","text":"Epoch 2/5: 100%|██████████| 1250/1250 [02:08<00:00,  9.76it/s, loss=0.0615]\n","output_type":"stream"},{"name":"stdout","text":"[Epoch 2] Train Loss: 0.1772 | Val Loss: 0.1921 | Val Acc: 93.44%\n","output_type":"stream"},{"name":"stderr","text":"Epoch 3/5: 100%|██████████| 1250/1250 [02:07<00:00,  9.80it/s, loss=0.0321] \n","output_type":"stream"},{"name":"stdout","text":"[Epoch 3] Train Loss: 0.1085 | Val Loss: 0.2042 | Val Acc: 93.61%\n","output_type":"stream"},{"name":"stderr","text":"Epoch 4/5: 100%|██████████| 1250/1250 [02:07<00:00,  9.83it/s, loss=0.0423] \n","output_type":"stream"},{"name":"stdout","text":"[Epoch 4] Train Loss: 0.0730 | Val Loss: 0.2307 | Val Acc: 93.24%\n","output_type":"stream"},{"name":"stderr","text":"Epoch 5/5: 100%|██████████| 1250/1250 [02:07<00:00,  9.83it/s, loss=0.0946]  \n","output_type":"stream"},{"name":"stdout","text":"[Epoch 5] Train Loss: 0.0597 | Val Loss: 0.2016 | Val Acc: 93.77%\n\n✅ Final Test Loss: 0.2066 | Test Accuracy: 93.80%\n","output_type":"stream"},{"output_type":"display_data","data":{"text/plain":"<Figure size 1200x500 with 2 Axes>","image/png":"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KBRCQkKCVF1okZb295///KewZcsW4ezZs8KpU6eEF154QZDL5cJvv/0mVRdaZM6cOYKrq6uwa9cuIS8vz3BUVVUZ2ljSZ7g1/TXnz/CCBQuE5ORk4eLFi8LJkyeFBQsWCDKZTPj1118FQbCseysILe+vOd9bU27cFcbS7q+14jjLcsdZHGNxjGVJn1+OsTjGspR725TOOs5iUqrep59+KnTv3l1QKpXC8OHDhYMHDxrORUdHC7NmzTJqv379eqFv376CUqkUBgwYIPz8888dHPGdaUl/58+fb2jr7e0tTJo0STh27JgEUbdOw3a8Nx4NfZw1a5YQHR1903PCwsIEpVIpBAUFCatWrerwuFurpf197733hF69egkqlUpwd3cXxo4dK+zYsUOa4FvBVF8BGN0zS/oMt6a/5vwZ/tOf/iT06NFDUCqVgqenp3D33XcbBg+CYFn3VhBa3l9zvrem3DhYsrT7a804zrLMcRbHWBxjWdLnl2MsjrEs5d42pbOOs2SCIAhtP/+KiIiIiIiIiIioaVZfU4qIiIiIiIiIiDoek1JERERERERERNThmJQiIiIiIiIiIqIOx6QUERERERERERF1OCaliIiIiIiIiIiowzEpRUREREREREREHY5JKSIiIiIiIiIi6nBMShERERERERERUYdjUoqIiIiIiIiIiDock1JERERERERERNThmJQiIiIiIiIiIqIOx6QUERERERERERF1uP8HmgfH+vpeGWAAAAAASUVORK5CYII=\n"},"metadata":{}},{"name":"stdout","text":"✅ 模型参数已保存：mobilenetv2_cifar10.pth\n✅ ONNX 模型已导出：mobilenetv2_cifar10.onnx\n","output_type":"stream"}],"execution_count":5},{"cell_type":"markdown","source":"混合精度模型转换","metadata":{}},{"cell_type":"markdown","source":"矩阵规模分析","metadata":{}},{"cell_type":"code","source":"#获取模型中所有 Conv2d 层的真实输入 shape\n\nimport torch\nimport torch.nn as nn\nfrom collections import defaultdict\n\ndef capture_conv_input_shapes(model, sample_input, device='cuda'):\n    \"\"\"\n    捕捉模型中所有 Conv2d 层的真实输入 shape（前向传播时）\n    \"\"\"\n    input_shapes = {}\n    hooks = []\n\n    # 定义 hook 函数\n    def hook_fn(name):\n        def fn(module, input, output):\n            if isinstance(input, tuple):\n                input_shapes[name] = tuple(input[0].shape)\n            else:\n                input_shapes[name] = tuple(input.shape)\n        return fn\n\n    # 注册 hook\n    for name, module in model.named_modules():\n        if isinstance(module, nn.Conv2d):\n            h = module.register_forward_hook(hook_fn(name))\n            hooks.append(h)\n\n    # 执行一次前向传播（捕捉 shape）\n    model = model.eval().to(device)\n    with torch.no_grad():\n        _ = model(sample_input.to(device))\n\n    # 移除 hook\n    for h in hooks:\n        h.remove()\n\n    return input_shapes\n\n\nfrom torchvision.models import mobilenet_v2\n\n# model = mobilenet_v2(pretrained=True).cuda().eval()\n\n# 用真实输入跑一遍\ndummy_input = torch.randn(1, 3, 224, 224).cuda()\nconv_input_shapes = capture_conv_input_shapes(model, dummy_input)\n\n# 打印部分结果\nfor name, shape in list(conv_input_shapes.items())[:5]:\n    print(f\"层 {name} 输入 shape: {shape}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-13T14:51:41.568743Z","iopub.execute_input":"2025-08-13T14:51:41.569024Z","iopub.status.idle":"2025-08-13T14:51:41.590189Z","shell.execute_reply.started":"2025-08-13T14:51:41.569004Z","shell.execute_reply":"2025-08-13T14:51:41.589555Z"},"scrolled":true},"outputs":[{"name":"stdout","text":"层 features.0.0 输入 shape: (1, 3, 224, 224)\n层 features.1.conv.0.0 输入 shape: (1, 32, 112, 112)\n层 features.1.conv.1 输入 shape: (1, 32, 112, 112)\n层 features.2.conv.0.0 输入 shape: (1, 16, 112, 112)\n层 features.2.conv.1.0 输入 shape: (1, 96, 112, 112)\n","output_type":"stream"}],"execution_count":26},{"cell_type":"markdown","source":"调度策略生成器","metadata":{}},{"cell_type":"code","source":"def should_use_tensorcore(layer_name, module, input_shape, output_shape):\n    \"\"\"\n    根据特征图大小和卷积类型决定是否使用 TensorCore\n    \"\"\"\n    # kH, kW = module.kernel_size\n    # groups = module.groups\n    C_in = input_shape[1]\n    # C_out = output_shape[1]\n    # H, W = input_shape[2], input_shape[3]\n\n    # 输出形状: (B, C_out, H_out, W_out)\n    _, C_out, H_out, W_out = output_shape\n    M = H_out * W_out\n    N = C_out\n    K = C_in * module.kernel_size[0] * module.kernel_size[1]\n    flops = 2 * M * N * K  # 近似 FLOPs\n\n\n    if module.groups == module.in_channels:\n        return False\n    if flops< 5e5:\n        return False\n    if (M % 8 == 0 and M >=32)or( N % 8 == 0 and N>=32)or (K%8==0 and K>=32):\n        return True\n    if M==1 and N>=512:\n        return True\n   # return False","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-13T14:51:44.322763Z","iopub.execute_input":"2025-08-13T14:51:44.323043Z","iopub.status.idle":"2025-08-13T14:51:44.328754Z","shell.execute_reply.started":"2025-08-13T14:51:44.323023Z","shell.execute_reply":"2025-08-13T14:51:44.327971Z"}},"outputs":[],"execution_count":27},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nfrom torchvision.models import mobilenet_v2\nimport pandas as pd\nimport json\n\ndef analyze_mobilenetv2_shapes(input_size=(3, 32, 32), device='cuda'):\n    \"\"\"\n    自动分析 MobileNetV2 各卷积层的矩阵规模 (M, N, K)\n    并保存为 CSV，同时给出 TensorCore 使用建议\n    \"\"\"\n    model = mobilenet_v2(pretrained=False).to(device)\n    model.eval()\n\n    layer_infos = []\n\n    def register_hook(module, name):\n        def hook(module, input, output):\n            if isinstance(module, nn.Conv2d):\n                # 输入形状: (B, C_in, H_in, W_in)\n                x = input[0]\n                _, C_in, _, _ = x.shape\n                # 输出形状: (B, C_out, H_out, W_out)\n                _, C_out, H_out, W_out = output.shape\n\n                # GEMM 等效矩阵规模\n                M = H_out * W_out\n                N = C_out\n                K = C_in * module.kernel_size[0] * module.kernel_size[1]\n                flops = 2 * M * N * K  # 近似 FLOPs\n\n                # TensorCore 使用策略：\n                # 1. 矩阵较大 (M*N*K > 2e6) 且 M、N、K 至少部分对齐8的倍数\n                # 2. Depthwise卷积(C_in=groups=C_out)通常不适合，除非合并\n                use_tc = False\n                if (flops>2e6 and module.groups == 1) or (flops > 2e5 and (M % 8 == 0 or N % 8 == 0 or K%8==0)):\n                    use_tc = True\n                if H_out*W_out ==1 and C_out >=512:\n                    use_tc = True\n                if module.groups==module.in_channels:\n                    use_tc =False\n                use_tc=False\n                #白名单\n                if name in [\"features.18.0\",\"features.1.conv.0.0\",\"features.3.conv.1.0\",\"features.5.conv.1.0\",\"features.17.conv.2\"]:\n                        use_tc = True\n                \n                # TensorCore 调度策略\n                # use_tc = should_use_tensorcore(name, module, x.shape, output.shape)\n\n\n                layer_infos.append({\n                    \"Layer\": name,\n                    \"Type\": type(module).__name__,\n                    \"Kernel\": f\"{module.kernel_size}\",\n                    \"Stride\": f\"{module.stride}\",\n                    \"Groups\": module.groups,\n                    \"InputShape\": f\"{tuple(x.shape)}\",\n                    \"OutputShape\": f\"{tuple(output.shape)}\",\n                    \"M\": M,\n                    \"N\": N,\n                    \"K\": K,\n                    \"FLOPs\": flops,\n                    \"UseTensorCore\": use_tc\n                })\n        return hook\n\n    # 给所有 Conv2d 注册 hook\n    for name, module in model.named_modules():\n        if isinstance(module, nn.Conv2d):\n            module.register_forward_hook(register_hook(module, name))\n\n    # 用一个假输入跑一遍前向推理，触发 hook\n    dummy_input = torch.randn(1, *input_size).to(device)\n    with torch.no_grad():\n        _ = model(dummy_input)\n\n    # 转为 DataFrame，按 FLOPs 排序\n    df = pd.DataFrame(layer_infos)\n    df = df.sort_values(\"FLOPs\", ascending=False)\n\n    # 保存为 CSV\n    csv_path = \"/kaggle/working/mobilenetv2_matrix_shapes2.csv\"\n    df.to_csv(csv_path, index=False)\n    print(f\"✅ 已保存矩阵规模与策略信息到 {csv_path}\")\n\n    # 生成调度策略 JSON {layer_name: bool}\n    strategy = {row[\"Layer\"]: bool(row[\"UseTensorCore\"]) for _, row in df.iterrows()}\n    with open(\"tensorcore_strategy.json\", \"w\") as f:\n        json.dump(strategy, f, indent=2)\n    print(f\"✅ 已保存 TensorCore 调度策略到 tensorcore_strategy.json\")\n\n    print(\"🔥 FLOPs 最大的前10层及建议策略：\")\n    print(df.head(100)[[\"Layer\", \"FLOPs\", \"UseTensorCore\"]])\n\n    return df\n\nif __name__ == \"__main__\":\n    analyze_mobilenetv2_shapes()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-13T14:51:51.145342Z","iopub.execute_input":"2025-08-13T14:51:51.145622Z","iopub.status.idle":"2025-08-13T14:51:51.253264Z","shell.execute_reply.started":"2025-08-13T14:51:51.145603Z","shell.execute_reply":"2025-08-13T14:51:51.252504Z"}},"outputs":[{"name":"stdout","text":"✅ 已保存矩阵规模与策略信息到 /kaggle/working/mobilenetv2_matrix_shapes2.csv\n✅ 已保存 TensorCore 调度策略到 tensorcore_strategy.json\n🔥 FLOPs 最大的前10层及建议策略：\n                   Layer     FLOPs  UseTensorCore\n7    features.3.conv.1.0  23887872           True\n34  features.12.conv.1.0  23887872          False\n37  features.13.conv.1.0  23887872          False\n43  features.15.conv.1.0  16588800          False\n49  features.17.conv.1.0  16588800          False\n46  features.16.conv.1.0  16588800          False\n22   features.8.conv.1.0  10616832          False\n28  features.10.conv.1.0  10616832          False\n25   features.9.conv.1.0  10616832          False\n13   features.5.conv.1.0  10616832           True\n4    features.2.conv.1.0  10616832          False\n31  features.11.conv.1.0  10616832          False\n16   features.6.conv.1.0  10616832          False\n10   features.4.conv.1.0   5971968          False\n40  features.14.conv.1.0   5971968          False\n1    features.1.conv.0.0   4718592           True\n19   features.7.conv.1.0   2654208          False\n51         features.18.0    819200           True\n3    features.2.conv.0.0    786432          False\n50    features.17.conv.2    614400           True\n39  features.14.conv.0.0    442368          False\n9    features.4.conv.0.0    442368          False\n0           features.0.0    442368          False\n8      features.3.conv.2    442368          False\n36  features.13.conv.0.0    442368          False\n33  features.12.conv.0.0    442368          False\n35    features.12.conv.2    442368          False\n38    features.13.conv.2    442368          False\n6    features.3.conv.0.0    442368          False\n44    features.15.conv.2    307200          False\n47    features.16.conv.2    307200          False\n42  features.15.conv.0.0    307200          False\n48  features.17.conv.0.0    307200          False\n45  features.16.conv.0.0    307200          False\n32    features.11.conv.2    294912          False\n5      features.2.conv.2    294912          False\n2      features.1.conv.1    262144          False\n18   features.7.conv.0.0    196608          False\n26     features.9.conv.2    196608          False\n17     features.6.conv.2    196608          False\n12   features.5.conv.0.0    196608          False\n15   features.6.conv.0.0    196608          False\n14     features.5.conv.2    196608          False\n21   features.8.conv.0.0    196608          False\n27  features.10.conv.0.0    196608          False\n29    features.10.conv.2    196608          False\n23     features.8.conv.2    196608          False\n30  features.11.conv.0.0    196608          False\n24   features.9.conv.0.0    196608          False\n41    features.14.conv.2    184320          False\n11     features.4.conv.2    147456          False\n20     features.7.conv.2     98304          False\n","output_type":"stream"}],"execution_count":29},{"cell_type":"markdown","source":"层级编译器","metadata":{}},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nimport torch_tensorrt\nimport pandas as pd\nimport torch_tensorrt\n\ntorch_tensorrt.runtime.set_multi_device_safe_mode(True)\n\nimport torch\nimport time\n#带计时的class\nclass TRTSubmoduleWrapper(torch.nn.Module):\n    def __init__(self, trt_module, name=\"TRTWrapper\",fp32_prefixes=None):\n        super().__init__()\n        self.trt_module = trt_module\n        self.name = name\n        self.last_runtime_ms = 0.0\n        self.fp32_prefixes = fp32_prefixes or []  # 用于自动映射\n\n\n    def forward(self, x):\n        # 记录开始时间\n        torch.cuda.synchronize()\n        start = time.time()\n\n        # 自动将输入从 FP32 转换为 FP16\n        if x.dtype == torch.float32:\n            x = x.half()\n\n        # 调用 TensorRT 推理\n        output = self.trt_module(x)\n\n        torch.cuda.synchronize()\n        end = time.time()\n        self.last_runtime_ms = (end - start) * 1000  # 记录本次执行耗时(ms)\n\n        # 输出转回 FP32，方便后续 PyTorch 层处理\n        return output.float()\n\ndef apply_tensorcore_strategy_with_shape_capture(model, input_tensor, export_path):\n    \"\"\"\n    自动捕捉 Conv2d 层输入 shape，尝试转换为 TensorRT 子模块，记录转换日志\n    \"\"\"\n    device = input_tensor.device\n    conv_shapes = capture_conv_input_shapes(model, input_tensor, device=device)\n\n    log = []\n\n    # 加载 TensorCore 调度策略\n\n\n    def convert_layer(name, module, input_shape, device=\"cuda\"):\n        \"\"\"\n        根据 JSON 调度表决定是否将模块转换为 TensorRT FP16\n        \"\"\"\n        status = \"跳过\"\n        error_msg = \"\"\n    \n        # Step 1: 判断 JSON 调度表是否启用 TensorCore\n        use_tensorcore = tc_strategy.get(name, False)\n        if not use_tensorcore:\n            error_msg = \"调度表禁用 TensorCore\"\n            return module, status, error_msg\n    \n        # Step 2: 额外通道数约束\n        if module.in_channels < 16 or module.out_channels < 16:\n            error_msg = \"输入或输出通道数过小\"\n            return module, status, error_msg\n    \n        # Step 3: 尝试 TensorRT 编译\n        try:\n            scripted = torch.jit.script(module.eval().to(device))\n            trt_mod = torch_tensorrt.compile(\n                scripted,\n                inputs=[\n                    torch_tensorrt.Input(\n                        min_shape=[1] + list(input_shape[1:]),\n                        opt_shape=[32] + list(input_shape[1:]),\n                        max_shape=[32] + list(input_shape[1:]),\n                        dtype=torch.float16\n                    )\n                ],\n                enabled_precisions={torch.float16},\n                truncate_long_and_double=True,\n                ir=\"torchscript\"\n            )\n            print(f\"✅ 编译 TRT 层: {name}, 输入 shape: {input_shape}\")\n            status = \"成功\"\n            return TRTSubmoduleWrapper(trt_mod, fp32_prefixes=[name]), status, error_msg\n    \n        except Exception as e:\n            status = \"失败\"\n            error_msg = str(e)\n            return module, status, error_msg\n\n    def recursive_apply(module, prefix=\"\"):\n        for name, submodule in module.named_children():\n            full_name = f\"{prefix}.{name}\" if prefix else name\n            if isinstance(submodule, nn.Conv2d) and full_name in conv_shapes:\n                new_mod, status, err = convert_layer(full_name, submodule, conv_shapes[full_name])\n                setattr(module, name, new_mod)\n                log.append({\n                    \"层名\": full_name,\n                    \"层结构\": str(submodule),\n                    \"状态\": status,\n                    \"错误信息\": err\n                })\n            else:\n                recursive_apply(submodule, full_name)\n\n    recursive_apply(model)\n\n    df = pd.DataFrame(log)\n    df.to_csv(export_path, index=False)\n    return model, df\n\n# 示例运行封装流程\n# model = mobilenet_v2(pretrained=True).eval().cuda()\ninput_tensor = torch.randn(32, 3, 224, 224).cuda()\nexport_path = \"/kaggle/working/mobilenet_tensorcore_log_v2.csv\"\n\nwith open(\"tensorcore_strategy.json\", \"r\") as f:\n    tc_strategy = json.load(f)\n    \nconverted_model, log_df = apply_tensorcore_strategy_with_shape_capture(model, input_tensor, export_path)\n# display(log_df.head())\ndisplay(log_df)\n\n# 测试推理\nwith torch.no_grad():\n    test_input = torch.randn(32, 3, 224, 224).cuda()\n    output = converted_model(test_input)\n    print(\"输出 shape:\", output.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-13T14:51:55.015871Z","iopub.execute_input":"2025-08-13T14:51:55.016231Z","iopub.status.idle":"2025-08-13T14:52:22.704631Z","shell.execute_reply.started":"2025-08-13T14:51:55.016195Z","shell.execute_reply":"2025-08-13T14:52:22.703939Z"}},"outputs":[{"name":"stderr","text":"WARNING: [Torch-TensorRT] - For input input.1, found user specified input dtype as Half, however when inspecting the graph, the input type expected was inferred to be Float\nThe compiler is going to use the user setting Half\nThis conflict may cause an error at runtime due to partial compilation being enabled and therefore\ncompatibility with PyTorch's data type convention is required.\nIf you do indeed see errors at runtime either:\n- Remove the dtype spec for input.1\n- Disable partial compilation by setting require_full_compilation to True\n","output_type":"stream"},{"name":"stdout","text":"✅ 编译 TRT 层: features.1.conv.0.0, 输入 shape: (32, 32, 112, 112)\n","output_type":"stream"},{"name":"stderr","text":"WARNING: [Torch-TensorRT] - For input input.1, found user specified input dtype as Half, however when inspecting the graph, the input type expected was inferred to be Float\nThe compiler is going to use the user setting Half\nThis conflict may cause an error at runtime due to partial compilation being enabled and therefore\ncompatibility with PyTorch's data type convention is required.\nIf you do indeed see errors at runtime either:\n- Remove the dtype spec for input.1\n- Disable partial compilation by setting require_full_compilation to True\n","output_type":"stream"},{"name":"stdout","text":"✅ 编译 TRT 层: features.3.conv.1.0, 输入 shape: (32, 144, 56, 56)\n","output_type":"stream"},{"name":"stderr","text":"WARNING: [Torch-TensorRT] - For input input.1, found user specified input dtype as Half, however when inspecting the graph, the input type expected was inferred to be Float\nThe compiler is going to use the user setting Half\nThis conflict may cause an error at runtime due to partial compilation being enabled and therefore\ncompatibility with PyTorch's data type convention is required.\nIf you do indeed see errors at runtime either:\n- Remove the dtype spec for input.1\n- Disable partial compilation by setting require_full_compilation to True\nWARNING: [Torch-TensorRT] - For input input.1, found user specified input dtype as Half, however when inspecting the graph, the input type expected was inferred to be Float\nThe compiler is going to use the user setting Half\nThis conflict may cause an error at runtime due to partial compilation being enabled and therefore\ncompatibility with PyTorch's data type convention is required.\nIf you do indeed see errors at runtime either:\n- Remove the dtype spec for input.1\n- Disable partial compilation by setting require_full_compilation to True\n","output_type":"stream"},{"name":"stdout","text":"✅ 编译 TRT 层: features.5.conv.1.0, 输入 shape: (32, 192, 28, 28)\n✅ 编译 TRT 层: features.17.conv.2, 输入 shape: (32, 960, 7, 7)\n✅ 编译 TRT 层: features.18.0, 输入 shape: (32, 320, 7, 7)\n","output_type":"stream"},{"name":"stderr","text":"WARNING: [Torch-TensorRT] - For input input.1, found user specified input dtype as Half, however when inspecting the graph, the input type expected was inferred to be Float\nThe compiler is going to use the user setting Half\nThis conflict may cause an error at runtime due to partial compilation being enabled and therefore\ncompatibility with PyTorch's data type convention is required.\nIf you do indeed see errors at runtime either:\n- Remove the dtype spec for input.1\n- Disable partial compilation by setting require_full_compilation to True\n","output_type":"stream"},{"output_type":"display_data","data":{"text/plain":"                      层名                                                层结构  \\\n0           features.0.0  Conv2d(3, 32, kernel_size=(3, 3), stride=(2, 2...   \n1    features.1.conv.0.0  Conv2d(32, 32, kernel_size=(3, 3), stride=(1, ...   \n2      features.1.conv.1  Conv2d(32, 16, kernel_size=(1, 1), stride=(1, ...   \n3    features.2.conv.0.0  Conv2d(16, 96, kernel_size=(1, 1), stride=(1, ...   \n4    features.2.conv.1.0  Conv2d(96, 96, kernel_size=(3, 3), stride=(2, ...   \n5      features.2.conv.2  Conv2d(96, 24, kernel_size=(1, 1), stride=(1, ...   \n6    features.3.conv.0.0  Conv2d(24, 144, kernel_size=(1, 1), stride=(1,...   \n7    features.3.conv.1.0  Conv2d(144, 144, kernel_size=(3, 3), stride=(1...   \n8      features.3.conv.2  Conv2d(144, 24, kernel_size=(1, 1), stride=(1,...   \n9    features.4.conv.0.0  Conv2d(24, 144, kernel_size=(1, 1), stride=(1,...   \n10   features.4.conv.1.0  Conv2d(144, 144, kernel_size=(3, 3), stride=(2...   \n11     features.4.conv.2  Conv2d(144, 32, kernel_size=(1, 1), stride=(1,...   \n12   features.5.conv.0.0  Conv2d(32, 192, kernel_size=(1, 1), stride=(1,...   \n13   features.5.conv.1.0  Conv2d(192, 192, kernel_size=(3, 3), stride=(1...   \n14     features.5.conv.2  Conv2d(192, 32, kernel_size=(1, 1), stride=(1,...   \n15   features.6.conv.0.0  Conv2d(32, 192, kernel_size=(1, 1), stride=(1,...   \n16   features.6.conv.1.0  Conv2d(192, 192, kernel_size=(3, 3), stride=(1...   \n17     features.6.conv.2  Conv2d(192, 32, kernel_size=(1, 1), stride=(1,...   \n18   features.7.conv.0.0  Conv2d(32, 192, kernel_size=(1, 1), stride=(1,...   \n19   features.7.conv.1.0  Conv2d(192, 192, kernel_size=(3, 3), stride=(2...   \n20     features.7.conv.2  Conv2d(192, 64, kernel_size=(1, 1), stride=(1,...   \n21   features.8.conv.0.0  Conv2d(64, 384, kernel_size=(1, 1), stride=(1,...   \n22   features.8.conv.1.0  Conv2d(384, 384, kernel_size=(3, 3), stride=(1...   \n23     features.8.conv.2  Conv2d(384, 64, kernel_size=(1, 1), stride=(1,...   \n24   features.9.conv.0.0  Conv2d(64, 384, kernel_size=(1, 1), stride=(1,...   \n25   features.9.conv.1.0  Conv2d(384, 384, kernel_size=(3, 3), stride=(1...   \n26     features.9.conv.2  Conv2d(384, 64, kernel_size=(1, 1), stride=(1,...   \n27  features.10.conv.0.0  Conv2d(64, 384, kernel_size=(1, 1), stride=(1,...   \n28  features.10.conv.1.0  Conv2d(384, 384, kernel_size=(3, 3), stride=(1...   \n29    features.10.conv.2  Conv2d(384, 64, kernel_size=(1, 1), stride=(1,...   \n30  features.11.conv.0.0  Conv2d(64, 384, kernel_size=(1, 1), stride=(1,...   \n31  features.11.conv.1.0  Conv2d(384, 384, kernel_size=(3, 3), stride=(1...   \n32    features.11.conv.2  Conv2d(384, 96, kernel_size=(1, 1), stride=(1,...   \n33  features.12.conv.0.0  Conv2d(96, 576, kernel_size=(1, 1), stride=(1,...   \n34  features.12.conv.1.0  Conv2d(576, 576, kernel_size=(3, 3), stride=(1...   \n35    features.12.conv.2  Conv2d(576, 96, kernel_size=(1, 1), stride=(1,...   \n36  features.13.conv.0.0  Conv2d(96, 576, kernel_size=(1, 1), stride=(1,...   \n37  features.13.conv.1.0  Conv2d(576, 576, kernel_size=(3, 3), stride=(1...   \n38    features.13.conv.2  Conv2d(576, 96, kernel_size=(1, 1), stride=(1,...   \n39  features.14.conv.0.0  Conv2d(96, 576, kernel_size=(1, 1), stride=(1,...   \n40  features.14.conv.1.0  Conv2d(576, 576, kernel_size=(3, 3), stride=(2...   \n41    features.14.conv.2  Conv2d(576, 160, kernel_size=(1, 1), stride=(1...   \n42  features.15.conv.0.0  Conv2d(160, 960, kernel_size=(1, 1), stride=(1...   \n43  features.15.conv.1.0  Conv2d(960, 960, kernel_size=(3, 3), stride=(1...   \n44    features.15.conv.2  Conv2d(960, 160, kernel_size=(1, 1), stride=(1...   \n45  features.16.conv.0.0  Conv2d(160, 960, kernel_size=(1, 1), stride=(1...   \n46  features.16.conv.1.0  Conv2d(960, 960, kernel_size=(3, 3), stride=(1...   \n47    features.16.conv.2  Conv2d(960, 160, kernel_size=(1, 1), stride=(1...   \n48  features.17.conv.0.0  Conv2d(160, 960, kernel_size=(1, 1), stride=(1...   \n49  features.17.conv.1.0  Conv2d(960, 960, kernel_size=(3, 3), stride=(1...   \n50    features.17.conv.2  Conv2d(960, 320, kernel_size=(1, 1), stride=(1...   \n51         features.18.0  Conv2d(320, 1280, kernel_size=(1, 1), stride=(...   \n\n    状态              错误信息  \n0   跳过  调度表禁用 TensorCore  \n1   成功                    \n2   跳过  调度表禁用 TensorCore  \n3   跳过  调度表禁用 TensorCore  \n4   跳过  调度表禁用 TensorCore  \n5   跳过  调度表禁用 TensorCore  \n6   跳过  调度表禁用 TensorCore  \n7   成功                    \n8   跳过  调度表禁用 TensorCore  \n9   跳过  调度表禁用 TensorCore  \n10  跳过  调度表禁用 TensorCore  \n11  跳过  调度表禁用 TensorCore  \n12  跳过  调度表禁用 TensorCore  \n13  成功                    \n14  跳过  调度表禁用 TensorCore  \n15  跳过  调度表禁用 TensorCore  \n16  跳过  调度表禁用 TensorCore  \n17  跳过  调度表禁用 TensorCore  \n18  跳过  调度表禁用 TensorCore  \n19  跳过  调度表禁用 TensorCore  \n20  跳过  调度表禁用 TensorCore  \n21  跳过  调度表禁用 TensorCore  \n22  跳过  调度表禁用 TensorCore  \n23  跳过  调度表禁用 TensorCore  \n24  跳过  调度表禁用 TensorCore  \n25  跳过  调度表禁用 TensorCore  \n26  跳过  调度表禁用 TensorCore  \n27  跳过  调度表禁用 TensorCore  \n28  跳过  调度表禁用 TensorCore  \n29  跳过  调度表禁用 TensorCore  \n30  跳过  调度表禁用 TensorCore  \n31  跳过  调度表禁用 TensorCore  \n32  跳过  调度表禁用 TensorCore  \n33  跳过  调度表禁用 TensorCore  \n34  跳过  调度表禁用 TensorCore  \n35  跳过  调度表禁用 TensorCore  \n36  跳过  调度表禁用 TensorCore  \n37  跳过  调度表禁用 TensorCore  \n38  跳过  调度表禁用 TensorCore  \n39  跳过  调度表禁用 TensorCore  \n40  跳过  调度表禁用 TensorCore  \n41  跳过  调度表禁用 TensorCore  \n42  跳过  调度表禁用 TensorCore  \n43  跳过  调度表禁用 TensorCore  \n44  跳过  调度表禁用 TensorCore  \n45  跳过  调度表禁用 TensorCore  \n46  跳过  调度表禁用 TensorCore  \n47  跳过  调度表禁用 TensorCore  \n48  跳过  调度表禁用 TensorCore  \n49  跳过  调度表禁用 TensorCore  \n50  成功                    \n51  成功                    ","text/html":"<div>\n<style scoped>\n    .dataframe tbody tr th:only-of-type {\n        vertical-align: middle;\n    }\n\n    .dataframe tbody tr th {\n        vertical-align: top;\n    }\n\n    .dataframe thead th {\n        text-align: right;\n    }\n</style>\n<table border=\"1\" class=\"dataframe\">\n  <thead>\n    <tr style=\"text-align: right;\">\n      <th></th>\n      <th>层名</th>\n      <th>层结构</th>\n      <th>状态</th>\n      <th>错误信息</th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <th>0</th>\n      <td>features.0.0</td>\n      <td>Conv2d(3, 32, kernel_size=(3, 3), stride=(2, 2...</td>\n      <td>跳过</td>\n      <td>调度表禁用 TensorCore</td>\n    </tr>\n    <tr>\n      <th>1</th>\n      <td>features.1.conv.0.0</td>\n      <td>Conv2d(32, 32, kernel_size=(3, 3), stride=(1, ...</td>\n      <td>成功</td>\n      <td></td>\n    </tr>\n    <tr>\n      <th>2</th>\n      <td>features.1.conv.1</td>\n      <td>Conv2d(32, 16, kernel_size=(1, 1), stride=(1, ...</td>\n      <td>跳过</td>\n      <td>调度表禁用 TensorCore</td>\n    </tr>\n    <tr>\n      <th>3</th>\n      <td>features.2.conv.0.0</td>\n      <td>Conv2d(16, 96, kernel_size=(1, 1), stride=(1, ...</td>\n      <td>跳过</td>\n      <td>调度表禁用 TensorCore</td>\n    </tr>\n    <tr>\n      <th>4</th>\n      <td>features.2.conv.1.0</td>\n      <td>Conv2d(96, 96, kernel_size=(3, 3), stride=(2, ...</td>\n      <td>跳过</td>\n      <td>调度表禁用 TensorCore</td>\n    </tr>\n    <tr>\n      <th>5</th>\n      <td>features.2.conv.2</td>\n      <td>Conv2d(96, 24, kernel_size=(1, 1), stride=(1, ...</td>\n      <td>跳过</td>\n      <td>调度表禁用 TensorCore</td>\n    </tr>\n    <tr>\n      <th>6</th>\n      <td>features.3.conv.0.0</td>\n      <td>Conv2d(24, 144, kernel_size=(1, 1), stride=(1,...</td>\n      <td>跳过</td>\n      <td>调度表禁用 TensorCore</td>\n    </tr>\n    <tr>\n      <th>7</th>\n      <td>features.3.conv.1.0</td>\n      <td>Conv2d(144, 144, kernel_size=(3, 3), stride=(1...</td>\n      <td>成功</td>\n      <td></td>\n    </tr>\n    <tr>\n      <th>8</th>\n      <td>features.3.conv.2</td>\n      <td>Conv2d(144, 24, kernel_size=(1, 1), stride=(1,...</td>\n      <td>跳过</td>\n      <td>调度表禁用 TensorCore</td>\n    </tr>\n    <tr>\n      <th>9</th>\n      <td>features.4.conv.0.0</td>\n      <td>Conv2d(24, 144, kernel_size=(1, 1), stride=(1,...</td>\n      <td>跳过</td>\n      <td>调度表禁用 TensorCore</td>\n    </tr>\n    <tr>\n      <th>10</th>\n      <td>features.4.conv.1.0</td>\n      <td>Conv2d(144, 144, kernel_size=(3, 3), stride=(2...</td>\n      <td>跳过</td>\n      <td>调度表禁用 TensorCore</td>\n    </tr>\n    <tr>\n      <th>11</th>\n      <td>features.4.conv.2</td>\n      <td>Conv2d(144, 32, kernel_size=(1, 1), stride=(1,...</td>\n      <td>跳过</td>\n      <td>调度表禁用 TensorCore</td>\n    </tr>\n    <tr>\n      <th>12</th>\n      <td>features.5.conv.0.0</td>\n      <td>Conv2d(32, 192, kernel_size=(1, 1), stride=(1,...</td>\n      <td>跳过</td>\n      <td>调度表禁用 TensorCore</td>\n    </tr>\n    <tr>\n      <th>13</th>\n      <td>features.5.conv.1.0</td>\n      <td>Conv2d(192, 192, kernel_size=(3, 3), stride=(1...</td>\n      <td>成功</td>\n      <td></td>\n    </tr>\n    <tr>\n      <th>14</th>\n      <td>features.5.conv.2</td>\n      <td>Conv2d(192, 32, kernel_size=(1, 1), stride=(1,...</td>\n      <td>跳过</td>\n      <td>调度表禁用 TensorCore</td>\n    </tr>\n    <tr>\n      <th>15</th>\n      <td>features.6.conv.0.0</td>\n      <td>Conv2d(32, 192, kernel_size=(1, 1), stride=(1,...</td>\n      <td>跳过</td>\n      <td>调度表禁用 TensorCore</td>\n    </tr>\n    <tr>\n      <th>16</th>\n      <td>features.6.conv.1.0</td>\n      <td>Conv2d(192, 192, kernel_size=(3, 3), stride=(1...</td>\n      <td>跳过</td>\n      <td>调度表禁用 TensorCore</td>\n    </tr>\n    <tr>\n      <th>17</th>\n      <td>features.6.conv.2</td>\n      <td>Conv2d(192, 32, kernel_size=(1, 1), stride=(1,...</td>\n      <td>跳过</td>\n      <td>调度表禁用 TensorCore</td>\n    </tr>\n    <tr>\n      <th>18</th>\n      <td>features.7.conv.0.0</td>\n      <td>Conv2d(32, 192, kernel_size=(1, 1), stride=(1,...</td>\n      <td>跳过</td>\n      <td>调度表禁用 TensorCore</td>\n    </tr>\n    <tr>\n      <th>19</th>\n      <td>features.7.conv.1.0</td>\n      <td>Conv2d(192, 192, kernel_size=(3, 3), stride=(2...</td>\n      <td>跳过</td>\n      <td>调度表禁用 TensorCore</td>\n    </tr>\n    <tr>\n      <th>20</th>\n      <td>features.7.conv.2</td>\n      <td>Conv2d(192, 64, kernel_size=(1, 1), stride=(1,...</td>\n      <td>跳过</td>\n      <td>调度表禁用 TensorCore</td>\n    </tr>\n    <tr>\n      <th>21</th>\n      <td>features.8.conv.0.0</td>\n      <td>Conv2d(64, 384, kernel_size=(1, 1), stride=(1,...</td>\n      <td>跳过</td>\n      <td>调度表禁用 TensorCore</td>\n    </tr>\n    <tr>\n      <th>22</th>\n      <td>features.8.conv.1.0</td>\n      <td>Conv2d(384, 384, kernel_size=(3, 3), stride=(1...</td>\n      <td>跳过</td>\n      <td>调度表禁用 TensorCore</td>\n    </tr>\n    <tr>\n      <th>23</th>\n      <td>features.8.conv.2</td>\n      <td>Conv2d(384, 64, kernel_size=(1, 1), stride=(1,...</td>\n      <td>跳过</td>\n      <td>调度表禁用 TensorCore</td>\n    </tr>\n    <tr>\n      <th>24</th>\n      <td>features.9.conv.0.0</td>\n      <td>Conv2d(64, 384, kernel_size=(1, 1), stride=(1,...</td>\n      <td>跳过</td>\n      <td>调度表禁用 TensorCore</td>\n    </tr>\n    <tr>\n      <th>25</th>\n      <td>features.9.conv.1.0</td>\n      <td>Conv2d(384, 384, kernel_size=(3, 3), stride=(1...</td>\n      <td>跳过</td>\n      <td>调度表禁用 TensorCore</td>\n    </tr>\n    <tr>\n      <th>26</th>\n      <td>features.9.conv.2</td>\n      <td>Conv2d(384, 64, kernel_size=(1, 1), stride=(1,...</td>\n      <td>跳过</td>\n      <td>调度表禁用 TensorCore</td>\n    </tr>\n    <tr>\n      <th>27</th>\n      <td>features.10.conv.0.0</td>\n      <td>Conv2d(64, 384, kernel_size=(1, 1), stride=(1,...</td>\n      <td>跳过</td>\n      <td>调度表禁用 TensorCore</td>\n    </tr>\n    <tr>\n      <th>28</th>\n      <td>features.10.conv.1.0</td>\n      <td>Conv2d(384, 384, kernel_size=(3, 3), stride=(1...</td>\n      <td>跳过</td>\n      <td>调度表禁用 TensorCore</td>\n    </tr>\n    <tr>\n      <th>29</th>\n      <td>features.10.conv.2</td>\n      <td>Conv2d(384, 64, kernel_size=(1, 1), stride=(1,...</td>\n      <td>跳过</td>\n      <td>调度表禁用 TensorCore</td>\n    </tr>\n    <tr>\n      <th>30</th>\n      <td>features.11.conv.0.0</td>\n      <td>Conv2d(64, 384, kernel_size=(1, 1), stride=(1,...</td>\n      <td>跳过</td>\n      <td>调度表禁用 TensorCore</td>\n    </tr>\n    <tr>\n      <th>31</th>\n      <td>features.11.conv.1.0</td>\n      <td>Conv2d(384, 384, kernel_size=(3, 3), stride=(1...</td>\n      <td>跳过</td>\n      <td>调度表禁用 TensorCore</td>\n    </tr>\n    <tr>\n      <th>32</th>\n      <td>features.11.conv.2</td>\n      <td>Conv2d(384, 96, kernel_size=(1, 1), stride=(1,...</td>\n      <td>跳过</td>\n      <td>调度表禁用 TensorCore</td>\n    </tr>\n    <tr>\n      <th>33</th>\n      <td>features.12.conv.0.0</td>\n      <td>Conv2d(96, 576, kernel_size=(1, 1), stride=(1,...</td>\n      <td>跳过</td>\n      <td>调度表禁用 TensorCore</td>\n    </tr>\n    <tr>\n      <th>34</th>\n      <td>features.12.conv.1.0</td>\n      <td>Conv2d(576, 576, kernel_size=(3, 3), stride=(1...</td>\n      <td>跳过</td>\n      <td>调度表禁用 TensorCore</td>\n    </tr>\n    <tr>\n      <th>35</th>\n      <td>features.12.conv.2</td>\n      <td>Conv2d(576, 96, kernel_size=(1, 1), stride=(1,...</td>\n      <td>跳过</td>\n      <td>调度表禁用 TensorCore</td>\n    </tr>\n    <tr>\n      <th>36</th>\n      <td>features.13.conv.0.0</td>\n      <td>Conv2d(96, 576, kernel_size=(1, 1), stride=(1,...</td>\n      <td>跳过</td>\n      <td>调度表禁用 TensorCore</td>\n    </tr>\n    <tr>\n      <th>37</th>\n      <td>features.13.conv.1.0</td>\n      <td>Conv2d(576, 576, kernel_size=(3, 3), stride=(1...</td>\n      <td>跳过</td>\n      <td>调度表禁用 TensorCore</td>\n    </tr>\n    <tr>\n      <th>38</th>\n      <td>features.13.conv.2</td>\n      <td>Conv2d(576, 96, kernel_size=(1, 1), stride=(1,...</td>\n      <td>跳过</td>\n      <td>调度表禁用 TensorCore</td>\n    </tr>\n    <tr>\n      <th>39</th>\n      <td>features.14.conv.0.0</td>\n      <td>Conv2d(96, 576, kernel_size=(1, 1), stride=(1,...</td>\n      <td>跳过</td>\n      <td>调度表禁用 TensorCore</td>\n    </tr>\n    <tr>\n      <th>40</th>\n      <td>features.14.conv.1.0</td>\n      <td>Conv2d(576, 576, kernel_size=(3, 3), stride=(2...</td>\n      <td>跳过</td>\n      <td>调度表禁用 TensorCore</td>\n    </tr>\n    <tr>\n      <th>41</th>\n      <td>features.14.conv.2</td>\n      <td>Conv2d(576, 160, kernel_size=(1, 1), stride=(1...</td>\n      <td>跳过</td>\n      <td>调度表禁用 TensorCore</td>\n    </tr>\n    <tr>\n      <th>42</th>\n      <td>features.15.conv.0.0</td>\n      <td>Conv2d(160, 960, kernel_size=(1, 1), stride=(1...</td>\n      <td>跳过</td>\n      <td>调度表禁用 TensorCore</td>\n    </tr>\n    <tr>\n      <th>43</th>\n      <td>features.15.conv.1.0</td>\n      <td>Conv2d(960, 960, kernel_size=(3, 3), stride=(1...</td>\n      <td>跳过</td>\n      <td>调度表禁用 TensorCore</td>\n    </tr>\n    <tr>\n      <th>44</th>\n      <td>features.15.conv.2</td>\n      <td>Conv2d(960, 160, kernel_size=(1, 1), stride=(1...</td>\n      <td>跳过</td>\n      <td>调度表禁用 TensorCore</td>\n    </tr>\n    <tr>\n      <th>45</th>\n      <td>features.16.conv.0.0</td>\n      <td>Conv2d(160, 960, kernel_size=(1, 1), stride=(1...</td>\n      <td>跳过</td>\n      <td>调度表禁用 TensorCore</td>\n    </tr>\n    <tr>\n      <th>46</th>\n      <td>features.16.conv.1.0</td>\n      <td>Conv2d(960, 960, kernel_size=(3, 3), stride=(1...</td>\n      <td>跳过</td>\n      <td>调度表禁用 TensorCore</td>\n    </tr>\n    <tr>\n      <th>47</th>\n      <td>features.16.conv.2</td>\n      <td>Conv2d(960, 160, kernel_size=(1, 1), stride=(1...</td>\n      <td>跳过</td>\n      <td>调度表禁用 TensorCore</td>\n    </tr>\n    <tr>\n      <th>48</th>\n      <td>features.17.conv.0.0</td>\n      <td>Conv2d(160, 960, kernel_size=(1, 1), stride=(1...</td>\n      <td>跳过</td>\n      <td>调度表禁用 TensorCore</td>\n    </tr>\n    <tr>\n      <th>49</th>\n      <td>features.17.conv.1.0</td>\n      <td>Conv2d(960, 960, kernel_size=(3, 3), stride=(1...</td>\n      <td>跳过</td>\n      <td>调度表禁用 TensorCore</td>\n    </tr>\n    <tr>\n      <th>50</th>\n      <td>features.17.conv.2</td>\n      <td>Conv2d(960, 320, kernel_size=(1, 1), stride=(1...</td>\n      <td>成功</td>\n      <td></td>\n    </tr>\n    <tr>\n      <th>51</th>\n      <td>features.18.0</td>\n      <td>Conv2d(320, 1280, kernel_size=(1, 1), stride=(...</td>\n      <td>成功</td>\n      <td></td>\n    </tr>\n  </tbody>\n</table>\n</div>"},"metadata":{}},{"name":"stdout","text":"输出 shape: torch.Size([32, 10])\n","output_type":"stream"}],"execution_count":30},{"cell_type":"markdown","source":"总耗时","metadata":{}},{"cell_type":"code","source":"# 2️⃣ 普通 PyTorch 模型计时（只记录总时间）\ndef profile_pytorch_model(model, input_tensor, amp=False, warmup=2):\n    model.eval().cuda()\n    with torch.no_grad():\n        for _ in range(warmup):\n            with torch.cuda.amp.autocast(enabled=amp):\n                _ = model(input_tensor)\n    torch.cuda.synchronize()\n    start = time.time()\n    with torch.no_grad():\n        with torch.cuda.amp.autocast(enabled=amp):\n            _ = model(input_tensor)\n    torch.cuda.synchronize()\n    end = time.time()\n    return (end - start) * 1000\n\n\n# 3️⃣ 混合部署计时（统计 wrapper 时间 + PyTorch 其他部分时间）\ndef profile_scriptmodule_with_wrappers(model, input_tensor, warmup=2):\n    model.eval().cuda()\n    with torch.no_grad():\n        for _ in range(warmup):\n            _ = model(input_tensor)\n\n    timed_wrappers = [(name, m) for name, m in model.named_modules()\n                      if isinstance(m, TRTSubmoduleWrapper)]\n    for _, m in timed_wrappers:\n        m.last_runtime_ms = 0.0\n\n    torch.cuda.synchronize()\n    start = time.time()\n    with torch.no_grad():\n        _ = model(input_tensor)\n    torch.cuda.synchronize()\n    end = time.time()\n    total_ms = (end - start) * 1000\n\n    trt_times = [(f\"[TensorRT]{name}\", m.last_runtime_ms) for name, m in timed_wrappers]\n    trt_total = sum(t for _, t in trt_times)\n    pytorch_other = max(total_ms - trt_total, 0.0)\n\n    df = pd.DataFrame(trt_times, columns=[\"Layer\", \"Time (ms)\"])\n    df = pd.concat([\n        df,\n        pd.DataFrame([\n            [\"[PyTorch Other]\", pytorch_other],\n            [\"[Total Model]\", total_ms],\n        ], columns=[\"Layer\", \"Time (ms)\"])\n    ], ignore_index=True)\n    df[\"Percent (%)\"] = df[\"Time (ms)\"] / total_ms * 100\n    return df, total_ms\n\n\n# 4️⃣ 三模式对比 + wrapper 加速比分析\ndef compare_three_modes_with_wrappers(model_fp32, model_mixed, trt_model, dummy_input):\n    # PyTorch FP32\n    fp32_time = profile_pytorch_model(model_fp32, dummy_input.float(), amp=False)\n\n    # PyTorch AMP (FP16)\n    # amp_time = profile_pytorch_model(model_mixed, dummy_input.float(), amp=True)\n\n    # 混合部署\n    trt_df, trt_time = profile_scriptmodule_with_wrappers(trt_model, dummy_input.float())\n\n    # === 总体对比 ===\n    df_summary = pd.DataFrame([\n        [\"PyTorch FP32\", fp32_time, 1.0],\n        # [\"PyTorch AMP(FP16)\", amp_time, fp32_time / amp_time],\n        [\"TensorRT Mixed\", trt_time, fp32_time / trt_time],\n    ], columns=[\"Mode\", \"Total Time (ms)\", \"Speedup x\"])\n\n    # === wrapper 逐个加速比 ===\n    wrapper_rows = []\n    for _, row in trt_df.iterrows():\n        name = row[\"Layer\"]\n        if name.startswith(\"[TensorRT]\"):\n            wrapper_time = row[\"Time (ms)\"]\n            wrapper_speedup = fp32_time / wrapper_time if wrapper_time > 0 else float(\"inf\")\n            wrapper_rows.append([name, wrapper_time, fp32_time,row[\"Percent (%)\"], wrapper_speedup])\n\n    df_wrappers = pd.DataFrame(wrapper_rows,\n                               columns=[\"TRT Layer\", \"Wrapper_Time (ms)\",\"fp32_times(ms)\",\"Percent (%)\", \"Speedup vs FP32 x\"])\n\n    return df_summary, trt_df, df_wrappers","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-13T14:52:40.272551Z","iopub.execute_input":"2025-08-13T14:52:40.272835Z","iopub.status.idle":"2025-08-13T14:52:40.286068Z","shell.execute_reply.started":"2025-08-13T14:52:40.272814Z","shell.execute_reply":"2025-08-13T14:52:40.285243Z"}},"outputs":[],"execution_count":31},{"cell_type":"code","source":"model = models.mobilenet_v2(weights=None)\nmodel.classifier[1] = nn.Linear(model.last_channel, 10)  # 10 类\nmodel.load_state_dict(torch.load('/kaggle/working/mobilenetv2_cifar10.pth'))\nmodel = model.to(device)\n\n# dummy_input = torch.randn(32, 3, 224, 224).cuda()\n# 从测试集取一批数据作为输入\nimages, labels = next(iter(test_loader))\ndummy_input = images.cuda()  \n\ndf_summary, df_trt, df_wrappers = compare_three_modes_with_wrappers(\n    model,       # PyTorch FP32 模型\n    model,       # 同一个模型用于 AMP\n    converted_model,  # ScriptModule + TimedTRTSubmoduleWrapper\n    dummy_input\n)\n\nprint(\"=== 推理总耗时对比 ===\")\nprint(df_summary)\n\nprint(\"\\n=== TensorRT 模块耗时明细 ===\")\nprint(df_trt)\n\n# print(\"\\n=== 每个 TensorRT wrapper 加速比 ===\")\n# print(df_wrappers)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-13T02:34:02.512283Z","iopub.execute_input":"2025-08-13T02:34:02.513094Z","iopub.status.idle":"2025-08-13T02:34:02.958734Z","shell.execute_reply.started":"2025-08-13T02:34:02.513069Z","shell.execute_reply":"2025-08-13T02:34:02.957645Z"}},"outputs":[{"name":"stdout","text":"=== 推理总耗时对比 ===\n             Mode  Total Time (ms)  Speedup x\n0    PyTorch FP32        36.417484   1.000000\n1  TensorRT Mixed        29.639006   1.228701\n\n=== TensorRT 模块耗时明细 ===\n                           Layer  Time (ms)  Percent (%)\n0  [TensorRT]features.1.conv.0.0   1.048803     3.538591\n1  [TensorRT]features.3.conv.1.0   1.181602     3.986647\n2  [TensorRT]features.5.conv.1.0   0.666380     2.248321\n3   [TensorRT]features.17.conv.2   0.498772     1.682822\n4        [TensorRT]features.18.0   0.512123     1.727869\n5                [PyTorch Other]  25.731325    86.815750\n6                  [Total Model]  29.639006   100.000000\n","output_type":"stream"}],"execution_count":21},{"cell_type":"markdown","source":"逐层计时","metadata":{}},{"cell_type":"code","source":"# -------------------------\n# 2️⃣ FP32 逐层计时（非 ScriptModule）\n# -------------------------\ndef profile_pytorch_layers(model, input_tensor, amp=False, warmup=2):\n    model.eval().cuda()\n    timing_data = {}\n\n    def make_hooks(name):\n        start_event = torch.cuda.Event(enable_timing=True)\n        end_event = torch.cuda.Event(enable_timing=True)\n\n        def pre_hook(mod, inputs):\n            torch.cuda.synchronize()\n            start_event.record()\n\n        def post_hook(mod, inputs, outputs):\n            end_event.record()\n            torch.cuda.synchronize()\n            elapsed = start_event.elapsed_time(end_event)  # ms\n            timing_data.setdefault(name, []).append(elapsed)\n        return pre_hook, post_hook\n\n    hooks = []\n    for name, module in model.named_modules():\n        if len(list(module.children())) == 0:  # 叶子层\n            pre, post = make_hooks(name)\n            hooks.append(module.register_forward_pre_hook(pre))\n            hooks.append(module.register_forward_hook(post))\n\n    # warmup\n    with torch.no_grad():\n        for _ in range(warmup):\n            with torch.cuda.amp.autocast(enabled=amp):\n                _ = model(input_tensor)\n\n    # 正式一次\n    with torch.no_grad():\n        with torch.cuda.amp.autocast(enabled=amp):\n            _ = model(input_tensor)\n\n    for h in hooks:\n        h.remove()\n\n    return {k: sum(v)/len(v) for k,v in timing_data.items()}\n\n\n# -------------------------\n# 3️⃣ 混合部署计时（ScriptModule）\n# -------------------------\ndef profile_scriptmodule_with_wrappers(model, input_tensor, warmup=2):\n    model.eval().cuda()\n    with torch.no_grad():\n        for _ in range(warmup):\n            _ = model(input_tensor)\n\n    wrappers = [(name, m) for name, m in model.named_modules()\n                if isinstance(m, TRTSubmoduleWrapper)]\n    for _, m in wrappers:\n        m.last_runtime_ms = 0.0\n\n    torch.cuda.synchronize()\n    start = time.time()\n    with torch.no_grad():\n        _ = model(input_tensor)\n    torch.cuda.synchronize()\n    end = time.time()\n    total_ms = (end - start) * 1000\n\n    trt_times = [(f\"[TensorRT]{name}\", m.last_runtime_ms) for name, m in wrappers]\n    trt_total = sum(t for _, t in trt_times)\n    pytorch_other = max(total_ms - trt_total, 0.0)\n\n    df = pd.DataFrame(trt_times, columns=[\"Layer\", \"Time (ms)\"])\n    df = pd.concat([\n        df,\n        pd.DataFrame([\n            [\"[PyTorch Other]\", pytorch_other],\n            [\"[Total Model]\", total_ms],\n        ], columns=[\"Layer\", \"Time (ms)\"])\n    ], ignore_index=True)\n    df[\"Percent (%)\"] = df[\"Time (ms)\"] / total_ms * 100\n    return df, total_ms\n\n\n# -------------------------\n# 4️⃣ 自动计算每个 wrapper 的真实加速比\n# -------------------------\ndef compute_wrapper_speedups(fp32_times, trt_model):\n    rows = []\n    for name, wrapper in trt_model.named_modules():\n        if isinstance(wrapper, TRTSubmoduleWrapper):\n            trt_time = wrapper.last_runtime_ms\n            if trt_time <= 0:\n                continue\n            # print(wrapper.fp32_prefixes)\n            # 自动匹配 FP32 子模块时间\n            fp32_sum = sum(t for k,t in fp32_times.items()\n                           if any(k.startswith(prefix) for prefix in wrapper.fp32_prefixes))\n\n            speedup = fp32_sum / trt_time if trt_time > 0 else float('inf')\n            rows.append([name, trt_time, fp32_sum, speedup])\n\n    return pd.DataFrame(rows, columns=[\"Wrapper\", \"TRT Time (ms)\", \"FP32 SubTime (ms)\", \"Speedup x\"])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-13T14:53:16.529552Z","iopub.execute_input":"2025-08-13T14:53:16.529830Z","iopub.status.idle":"2025-08-13T14:53:16.543351Z","shell.execute_reply.started":"2025-08-13T14:53:16.529810Z","shell.execute_reply":"2025-08-13T14:53:16.542731Z"}},"outputs":[],"execution_count":32},{"cell_type":"code","source":" # model = mobilenet_v2(pretrained=True).eval().cuda()\n# ========== 测试：构建 MobileNetV2 模型 ==========\nmodel = models.mobilenet_v2(weights=None)\nmodel.classifier[1] = nn.Linear(model.last_channel, 10)  # 10 类\nmodel.load_state_dict(torch.load('/kaggle/working/mobilenetv2_cifar10.pth'))\nmodel = model.to(device)\n# dummy_input = torch.randn(32, 3, 224, 224).cuda()\n# 从测试集取一批数据作为输入\nimages, labels = next(iter(test_loader))\ndummy_input = images.cuda()  \n\n# 1️⃣ 记录 FP32 逐层时间\nprint(type(model))\n\nfp32_times = profile_pytorch_layers(model, dummy_input, amp=False)\n# print(fp32_times)\n# 2️⃣ 记录混合部署 wrapper 时间\ntrt_df, trt_total = profile_scriptmodule_with_wrappers(converted_model, dummy_input)\nprint(trt_total)\n# 3️⃣ 计算自动真实逐 wrapper 加速比\ndf_wrappers = compute_wrapper_speedups(fp32_times, converted_model)\n\nprint(\"=== TensorRT wrapper 加速比 ===\")\nprint(df_wrappers)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-13T14:53:27.364654Z","iopub.execute_input":"2025-08-13T14:53:27.364958Z","iopub.status.idle":"2025-08-13T14:53:27.831462Z","shell.execute_reply.started":"2025-08-13T14:53:27.364936Z","shell.execute_reply":"2025-08-13T14:53:27.830642Z"}},"outputs":[{"name":"stdout","text":"<class 'torchvision.models.mobilenetv2.MobileNetV2'>\n29.922962188720703\n=== TensorRT wrapper 加速比 ===\n               Wrapper  TRT Time (ms)  FP32 SubTime (ms)  Speedup x\n0  features.1.conv.0.0       1.006603           1.617237   1.606628\n1  features.3.conv.1.0       1.154423           1.790752   1.551210\n2  features.5.conv.1.0       0.633955           0.630475   0.994510\n3   features.17.conv.2       0.460863           0.536085   1.163220\n4        features.18.0       0.490904           0.680437   1.386091\n","output_type":"stream"}],"execution_count":35},{"cell_type":"code","source":"# -------------------------\n# 2️⃣ FP32 逐层计时（整个测试集）\n# -------------------------\ndef profile_pytorch_layers_fullset(model, dataloader, amp=False, warmup=2):\n    model.eval().cuda()\n    timing_data = {}\n\n    def make_hooks(name):\n        start_event = torch.cuda.Event(enable_timing=True)\n        end_event = torch.cuda.Event(enable_timing=True)\n\n        def pre_hook(mod, inputs):\n            torch.cuda.synchronize()\n            start_event.record()\n\n        def post_hook(mod, inputs, outputs):\n            end_event.record()\n            torch.cuda.synchronize()\n            elapsed = start_event.elapsed_time(end_event)  # ms\n            timing_data.setdefault(name, []).append(elapsed)\n        return pre_hook, post_hook\n\n    hooks = []\n    for name, module in model.named_modules():\n        if len(list(module.children())) == 0:  # 叶子层\n            pre, post = make_hooks(name)\n            hooks.append(module.register_forward_pre_hook(pre))\n            hooks.append(module.register_forward_hook(post))\n\n    # warmup（用第一批数据）\n    images, _ = next(iter(dataloader))\n    images = images.cuda(non_blocking=True)\n    with torch.no_grad():\n        for _ in range(warmup):\n            with torch.cuda.amp.autocast(enabled=amp):\n                _ = model(images)\n\n    # 正式全测试集计时\n    with torch.no_grad():\n        for images, _ in dataloader:\n            images = images.cuda(non_blocking=True)\n            with torch.cuda.amp.autocast(enabled=amp):\n                _ = model(images)\n\n    for h in hooks:\n        h.remove()\n\n    # 计算平均时间\n    return {k: sum(v)/len(v) for k, v in timing_data.items()}\n\n\n# -------------------------\n# 3️⃣ 混合部署计时（整个测试集）\n# -------------------------\ndef profile_scriptmodule_with_wrappers_fullset(model, dataloader, warmup=2):\n    model.eval().cuda()\n\n    # warmup\n    images, _ = next(iter(dataloader))\n    images = images.cuda(non_blocking=True)\n    with torch.no_grad():\n        for _ in range(warmup):\n            _ = model(images)\n\n    wrappers = [(name, m) for name, m in model.named_modules()\n                if isinstance(m, TRTSubmoduleWrapper)]\n    for _, m in wrappers:\n        m.last_runtime_ms = 0.0\n\n    torch.cuda.synchronize()\n    start = time.time()\n    with torch.no_grad():\n        for images, _ in dataloader:\n            images = images.cuda(non_blocking=True)\n            _ = model(images)\n    torch.cuda.synchronize()\n    end = time.time()\n\n    total_ms = (end - start) * 1000\n    trt_times = [(f\"[TensorRT]{name}\", m.last_runtime_ms) for name, m in wrappers]\n    trt_total = sum(t for _, t in trt_times)\n    pytorch_other = max(total_ms - trt_total, 0.0)\n\n    df = pd.DataFrame(trt_times, columns=[\"Layer\", \"Time (ms)\"])\n    df = pd.concat([\n        df,\n        pd.DataFrame([\n            [\"[PyTorch Other]\", pytorch_other],\n            [\"[Total Model]\", total_ms],\n        ], columns=[\"Layer\", \"Time (ms)\"])\n    ], ignore_index=True)\n    df[\"Percent (%)\"] = df[\"Time (ms)\"] / total_ms * 100\n    return df, total_ms\n\n\n# =======================\n# 使用全测试集版本\n# =======================\nfp32_times = profile_pytorch_layers_fullset(model, test_loader, amp=False)\ntrt_df, trt_total = profile_scriptmodule_with_wrappers_fullset(converted_model, test_loader)\ndf_wrappers = compute_wrapper_speedups(fp32_times, converted_model)\n\nprint(\"=== TensorRT wrapper 加速比 ===\")\nprint(df_wrappers)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-13T03:07:40.809129Z","iopub.execute_input":"2025-08-13T03:07:40.809409Z","iopub.status.idle":"2025-08-13T03:08:48.765871Z","shell.execute_reply.started":"2025-08-13T03:07:40.809390Z","shell.execute_reply":"2025-08-13T03:08:48.765001Z"}},"outputs":[{"name":"stdout","text":"=== TensorRT wrapper 加速比 ===\n                 Wrapper  TRT Time (ms)  FP32 SubTime (ms)  Speedup x\n0    features.1.conv.0.0       0.886679           1.628452   1.836575\n1      features.1.conv.1       0.842333           0.591926   0.702722\n2    features.2.conv.0.0       1.106024           1.325772   1.198683\n3    features.2.conv.1.0       1.100063           1.432820   1.302489\n4      features.2.conv.2       0.748634           0.390045   0.521008\n5    features.3.conv.0.0       0.765085           0.611402   0.799130\n6    features.3.conv.1.0       0.890255           1.797555   2.019146\n7      features.3.conv.2       0.839472           0.497563   0.592709\n8    features.4.conv.0.0       0.756741           0.613918   0.811266\n9    features.4.conv.1.0       0.638962           0.605667   0.947893\n10     features.4.conv.2       0.563383           0.213117   0.378281\n11   features.5.conv.0.0       0.528574           0.278897   0.527641\n12   features.5.conv.1.0       0.552893           0.635770   1.149897\n13     features.5.conv.2       0.566006           0.254943   0.450426\n14   features.6.conv.0.0       0.514507           0.265963   0.516927\n15   features.6.conv.1.0       0.582218           0.635811   1.092050\n16     features.6.conv.2       0.573397           0.251798   0.439134\n17   features.7.conv.0.0       0.481844           0.262774   0.545350\n18   features.7.conv.1.0       0.494242           0.255165   0.516276\n19     features.7.conv.2       0.529051           0.175937   0.332551\n20   features.8.conv.0.0       0.538349           0.234588   0.435754\n21   features.8.conv.1.0       0.530481           0.347199   0.654499\n22     features.8.conv.2       0.531912           0.259177   0.487256\n23   features.9.conv.0.0       0.498533           0.234698   0.470777\n24   features.9.conv.1.0       0.551224           0.346043   0.627772\n25     features.9.conv.2       0.528812           0.258327   0.488505\n26  features.10.conv.0.0       0.490189           0.233605   0.476561\n27  features.10.conv.1.0       0.550508           0.346193   0.628860\n28    features.10.conv.2       0.530243           0.256312   0.483386\n29  features.11.conv.0.0       0.478029           0.231601   0.484492\n30  features.11.conv.1.0       0.521183           0.346357   0.664559\n31    features.11.conv.2       0.516891           0.319048   0.617244\n32  features.12.conv.0.0       0.502110           0.448993   0.894212\n33  features.12.conv.1.0       0.599146           0.489938   0.817727\n34    features.12.conv.2       0.552893           0.437062   0.790500\n35  features.13.conv.0.0       0.508785           0.444865   0.874367\n36  features.13.conv.1.0       0.612259           0.490418   0.800998\n37    features.13.conv.2       0.569344           0.436157   0.766070\n38  features.14.conv.0.0       0.521183           0.444000   0.851908\n39  features.14.conv.1.0       0.513792           0.212914   0.414397\n40    features.14.conv.2       0.517607           0.254521   0.491727\n41  features.15.conv.0.0       0.526667           0.296745   0.563440\n42  features.15.conv.1.0       0.516653           0.236904   0.458537\n43    features.15.conv.2       0.493288           0.355269   0.720207\n44  features.16.conv.0.0       0.521660           0.296351   0.568093\n45  features.16.conv.1.0       0.482082           0.236901   0.491411\n46    features.16.conv.2       0.488997           0.353464   0.722835\n47  features.17.conv.0.0       0.547171           0.295218   0.539535\n48  features.17.conv.1.0       0.508547           0.235997   0.464061\n49    features.17.conv.2       0.490904           0.544769   1.109725\n50         features.18.0       0.508547           0.692710   1.362136\n","output_type":"stream"}],"execution_count":33},{"cell_type":"markdown","source":"准确率评估","metadata":{}},{"cell_type":"code","source":"# ========== model测试集评估 ==========\nmodel.eval()\ntest_loss = 0.0\ncorrect, total = 0, 0\n\nwith torch.no_grad():\n    for inputs, labels in test_loader:\n        inputs, labels = inputs.to(device), labels.to(device)\n        outputs = model(inputs)\n        loss = criterion(outputs, labels)\n        test_loss += loss.item()\n\n        preds = outputs.argmax(dim=1)\n        correct += (preds == labels).sum().item()\n        total += labels.size(0)\n\navg_test_loss = test_loss / len(test_loader)\ntest_acc = correct / total\nprint(f\"\\n✅ model Test Loss: {avg_test_loss:.4f} | Test Accuracy: {test_acc*100:.2f}%\")\n\n# ========== converted_model测试集评估 ==========\nconverted_model.eval()\ntest_loss = 0.0\ncorrect, total = 0, 0\n\nwith torch.no_grad():\n    for inputs, labels in test_loader:\n        inputs, labels = inputs.to(device), labels.to(device)\n        outputs = converted_model(inputs)\n        loss = criterion(outputs, labels)\n        test_loss += loss.item()\n\n        preds = outputs.argmax(dim=1)\n        correct += (preds == labels).sum().item()\n        total += labels.size(0)\n\navg_test_loss = test_loss / len(test_loader)\ntest_acc = correct / total\nprint(f\"\\n✅ converted_model Test Loss: {avg_test_loss:.4f} | Test Accuracy: {test_acc*100:.2f}%\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-13T03:36:56.604981Z","iopub.execute_input":"2025-08-13T03:36:56.605295Z","iopub.status.idle":"2025-08-13T03:37:46.986025Z","shell.execute_reply.started":"2025-08-13T03:36:56.605274Z","shell.execute_reply":"2025-08-13T03:37:46.985095Z"}},"outputs":[{"name":"stdout","text":"\n✅ model Test Loss: 0.2115 | Test Accuracy: 93.55%\n\n✅ converted_model Test Loss: 0.2118 | Test Accuracy: 93.54%\n","output_type":"stream"}],"execution_count":77},{"cell_type":"code","source":"import subprocess\nimport pandas as pd\nimport re\n\n# 你要分析的核心指标\nMETRICS = [\n    \"sm__throughput.avg.pct_of_peak_sustained_elapsed\",  # SM 占用率\n    \"tensor_pipe_tensor_op_hmma_cycles_active.avg.pct_of_peak_sustained_elapsed\"  # TensorCore 利用率\n]\n\ndef profile_inference(script=\"/kaggle/input/tensor/tensorcore.py\", output_csv=\"/kaggle/working/gpu_profile.csv\"):\n    cmd = [\n        \"ncu\",\n        \"--metrics\", \",\".join(METRICS),\n        \"--csv\",            # CSV 格式便于解析\n        \"python\", script\n    ]\n\n    print(f\"🔥 Profiling {script} with Nsight Compute...\")\n    result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)\n\n    # ncu 输出是 CSV，解析有点复杂，我们只取关键行\n    lines = [line for line in result.stdout.splitlines() if re.search(r\"[0-9]+(\\.[0-9]+)?\", line)]\n    \n    # 保存 CSV\n    with open(output_csv, \"w\") as f:\n        f.write(\"\\n\".join(lines))\n    print(f\"✅ Profile saved to {output_csv}\")\n\n    # 读入 DataFrame 方便后处理\n    df = pd.read_csv(output_csv)\n    print(df.head(10))\n    return df\n\nif __name__ == \"__main__\":\n    df = profile_inference()\n    sm_util = df[METRICS[0]].astype(float).mean()\n    tc_util = df[METRICS[1]].astype(float).mean()\n    print(f\"平均 SM 占用率: {sm_util:.2f}%\")\n    print(f\"平均 TensorCore 利用率: {tc_util:.2f}%\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-13T02:13:47.079317Z","iopub.status.idle":"2025-08-13T02:13:47.079635Z","execution_failed":"2025-08-13T06:52:05.398Z"}},"outputs":[],"execution_count":null}]}